From 5eef8a8bcf44101cb83e95af21f5f082eb4b7c6b Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 13 Apr 2016 09:56:10 +0300 Subject: [PATCH 01/15] Make it possible to get DB backend of a module --- src/gen_mod.erl | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/src/gen_mod.erl b/src/gen_mod.erl index 1044d0953..a290aa20a 100644 --- a/src/gen_mod.erl +++ b/src/gen_mod.erl @@ -36,7 +36,7 @@ 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, - default_db/1, v_db/1, opt_type/1]). + default_db/1, v_db/1, opt_type/1, db_mod/2, db_mod/3]). %%-export([behaviour_info/1]). @@ -319,6 +319,19 @@ db_type(Host, Opts) when is_list(Opts) -> default_db(Host) -> ejabberd_config:get_option({default_db, Host}, fun v_db/1, mnesia). +-spec db_mod(binary() | global | db_type(), module()) -> module(). + +db_mod(odbc, Module) -> list_to_atom(atom_to_list(Module) ++ "_sql"); +db_mod(mnesia, Module) -> list_to_atom(atom_to_list(Module) ++ "_mnesia"); +db_mod(riak, Module) -> list_to_atom(atom_to_list(Module) ++ "_riak"); +db_mod(Host, Module) when is_binary(Host) orelse Host == global -> + db_mod(db_type(Host, Module), Module). + +-spec db_mod(binary() | global, opts(), module()) -> module(). + +db_mod(Host, Opts, Module) when is_list(Opts) -> + db_mod(db_type(Host, Opts), Module). + -spec loaded_modules(binary()) -> [atom()]. loaded_modules(Host) -> From 7fd4808cdea6d22cd520bbfd1d0e12344fea7092 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 13 Apr 2016 09:59:39 +0300 Subject: [PATCH 02/15] Clean mod_last.erl from DB specific code --- include/mod_last.hrl | 3 + src/mod_last.erl | 152 +++++++--------------------------------- src/mod_last_mnesia.erl | 72 +++++++++++++++++++ src/mod_last_riak.erl | 53 ++++++++++++++ src/mod_last_sql.erl | 75 ++++++++++++++++++++ 5 files changed, 227 insertions(+), 128 deletions(-) create mode 100644 include/mod_last.hrl create mode 100644 src/mod_last_mnesia.erl create mode 100644 src/mod_last_riak.erl create mode 100644 src/mod_last_sql.erl diff --git a/include/mod_last.hrl b/include/mod_last.hrl new file mode 100644 index 000000000..494bf7b0c --- /dev/null +++ b/include/mod_last.hrl @@ -0,0 +1,3 @@ +-record(last_activity, {us = {<<"">>, <<"">>} :: {binary(), binary()}, + timestamp = 0 :: non_neg_integer(), + status = <<"">> :: binary()}). diff --git a/src/mod_last.erl b/src/mod_last.erl index 009a1cb06..1af1847b3 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -45,23 +45,21 @@ -include("jlib.hrl"). -include("mod_privacy.hrl"). +-include("mod_last.hrl"). --record(last_activity, {us = {<<"">>, <<"">>} :: {binary(), binary()}, - timestamp = 0 :: non_neg_integer(), - status = <<"">> :: binary()}). +-callback init(binary(), gen_mod:opts()) -> any(). +-callback import(binary(), #last_activity{}) -> ok | pass. +-callback get_last(binary(), binary()) -> + {ok, non_neg_integer(), binary()} | not_found | {error, any()}. +-callback store_last_info(binary(), binary(), non_neg_integer(), binary()) -> + {atomic, any()}. +-callback remove_user(binary(), binary()) -> {atomic, any()}. start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, one_queue), - case gen_mod:db_type(Host, Opts) of - mnesia -> - mnesia:create_table(last_activity, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, last_activity)}]), - update_table(); - _ -> ok - end, + Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod:init(Host, Opts), gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_LAST, ?MODULE, process_local_iq, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, @@ -163,38 +161,8 @@ process_sm_iq(From, To, %% @spec (LUser::string(), LServer::string()) -> %% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason} get_last(LUser, LServer) -> - get_last(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). - -get_last(LUser, LServer, mnesia) -> - case catch mnesia:dirty_read(last_activity, - {LUser, LServer}) - of - {'EXIT', Reason} -> {error, Reason}; - [] -> not_found; - [#last_activity{timestamp = TimeStamp, - status = Status}] -> - {ok, TimeStamp, Status} - end; -get_last(LUser, LServer, riak) -> - case ejabberd_riak:get(last_activity, last_activity_schema(), - {LUser, LServer}) of - {ok, #last_activity{timestamp = TimeStamp, - status = Status}} -> - {ok, TimeStamp, Status}; - {error, notfound} -> - not_found; - Err -> - Err - end; -get_last(LUser, LServer, odbc) -> - case catch odbc_queries:get_last(LServer, LUser) of - {selected, []} -> - not_found; - {selected, [{TimeStamp, Status}]} -> - {ok, TimeStamp, Status}; - Reason -> {error, {invalid_result, Reason}} - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:get_last(LUser, LServer). get_last_iq(#iq{lang = Lang} = IQ, SubEl, LUser, LServer) -> case ejabberd_sm:get_user_resources(LUser, LServer) of @@ -237,29 +205,8 @@ on_presence_update(User, Server, _Resource, Status) -> store_last_info(User, Server, TimeStamp, Status) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - DBType = gen_mod:db_type(LServer, ?MODULE), - store_last_info(LUser, LServer, TimeStamp, Status, - DBType). - -store_last_info(LUser, LServer, TimeStamp, Status, - mnesia) -> - US = {LUser, LServer}, - F = fun () -> - mnesia:write(#last_activity{us = US, - timestamp = TimeStamp, - status = Status}) - end, - mnesia:transaction(F); -store_last_info(LUser, LServer, TimeStamp, Status, - riak) -> - US = {LUser, LServer}, - {atomic, ejabberd_riak:put(#last_activity{us = US, - timestamp = TimeStamp, - status = Status}, - last_activity_schema())}; -store_last_info(LUser, LServer, TimeStamp, Status, - odbc) -> - odbc_queries:set_last_t(LServer, LUser, TimeStamp, Status). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:store_last_info(LUser, LServer, TimeStamp, Status). %% @spec (LUser::string(), LServer::string()) -> %% {ok, TimeStamp::integer(), Status::string()} | not_found @@ -272,71 +219,20 @@ get_last_info(LUser, LServer) -> remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - DBType = gen_mod:db_type(LServer, ?MODULE), - remove_user(LUser, LServer, DBType). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:remove_user(LUser, LServer). -remove_user(LUser, LServer, mnesia) -> - US = {LUser, LServer}, - F = fun () -> mnesia:delete({last_activity, US}) end, - mnesia:transaction(F); -remove_user(LUser, LServer, odbc) -> - odbc_queries:del_last(LServer, LUser); -remove_user(LUser, LServer, riak) -> - {atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}. - -update_table() -> - Fields = record_info(fields, last_activity), - case mnesia:table_info(last_activity, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - last_activity, Fields, set, - fun(#last_activity{us = {U, _}}) -> U end, - fun(#last_activity{us = {U, S}, status = Status} = R) -> - R#last_activity{us = {iolist_to_binary(U), - iolist_to_binary(S)}, - status = iolist_to_binary(Status)} - end); - _ -> - ?INFO_MSG("Recreating last_activity table", []), - mnesia:transform_table(last_activity, ignore, Fields) - end. - -last_activity_schema() -> - {record_info(fields, last_activity), #last_activity{}}. - -export(_Server) -> - [{last_activity, - fun(Host, #last_activity{us = {LUser, LServer}, - timestamp = TimeStamp, status = Status}) - when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - Seconds = - ejabberd_odbc:escape(jlib:integer_to_binary(TimeStamp)), - State = ejabberd_odbc:escape(Status), - [[<<"delete from last where username='">>, Username, <<"';">>], - [<<"insert into last(username, seconds, " - "state) values ('">>, - Username, <<"', '">>, Seconds, <<"', '">>, State, - <<"');">>]]; - (_Host, _R) -> - [] - end}]. +export(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:export(LServer). import(LServer) -> - [{<<"select username, seconds, state from last">>, - fun([LUser, TimeStamp, State]) -> - #last_activity{us = {LUser, LServer}, - timestamp = jlib:binary_to_integer( - TimeStamp), - status = State} - end}]. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:import(LServer). -import(_LServer, mnesia, #last_activity{} = LA) -> - mnesia:dirty_write(LA); -import(_LServer, riak, #last_activity{} = LA) -> - ejabberd_riak:put(LA, last_activity_schema()); -import(_, _, _) -> - pass. +import(LServer, DBType, LA) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, LA). transform_options(Opts) -> lists:foldl(fun transform_options/2, [], Opts). diff --git a/src/mod_last_mnesia.erl b/src/mod_last_mnesia.erl new file mode 100644 index 000000000..7a1610abb --- /dev/null +++ b/src/mod_last_mnesia.erl @@ -0,0 +1,72 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_last_mnesia). +-behaviour(mod_last). + +%% API +-export([init/2, import/2, get_last/2, store_last_info/4, remove_user/2]). + +-include("mod_last.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + mnesia:create_table(last_activity, + [{disc_copies, [node()]}, + {attributes, + record_info(fields, last_activity)}]), + update_table(). + +get_last(LUser, LServer) -> + case mnesia:dirty_read(last_activity, {LUser, LServer}) of + [] -> + not_found; + [#last_activity{timestamp = TimeStamp, + status = Status}] -> + {ok, TimeStamp, Status} + end. + +store_last_info(LUser, LServer, TimeStamp, Status) -> + US = {LUser, LServer}, + F = fun () -> + mnesia:write(#last_activity{us = US, + timestamp = TimeStamp, + status = Status}) + end, + mnesia:transaction(F). + +remove_user(LUser, LServer) -> + US = {LUser, LServer}, + F = fun () -> mnesia:delete({last_activity, US}) end, + mnesia:transaction(F). + +import(_LServer, #last_activity{} = LA) -> + mnesia:dirty_write(LA). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +update_table() -> + Fields = record_info(fields, last_activity), + case mnesia:table_info(last_activity, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + last_activity, Fields, set, + fun(#last_activity{us = {U, _}}) -> U end, + fun(#last_activity{us = {U, S}, status = Status} = R) -> + R#last_activity{us = {iolist_to_binary(U), + iolist_to_binary(S)}, + status = iolist_to_binary(Status)} + end); + _ -> + ?INFO_MSG("Recreating last_activity table", []), + mnesia:transform_table(last_activity, ignore, Fields) + end. diff --git a/src/mod_last_riak.erl b/src/mod_last_riak.erl new file mode 100644 index 000000000..d25a3a156 --- /dev/null +++ b/src/mod_last_riak.erl @@ -0,0 +1,53 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_last_riak). +-behaviour(mod_last). + +%% API +-export([init/2, import/2, get_last/2, store_last_info/4, remove_user/2]). + +-include("mod_last.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +get_last(LUser, LServer) -> + case ejabberd_riak:get(last_activity, last_activity_schema(), + {LUser, LServer}) of + {ok, #last_activity{timestamp = TimeStamp, + status = Status}} -> + {ok, TimeStamp, Status}; + {error, notfound} -> + not_found; + Err -> + Err + end. + +store_last_info(LUser, LServer, TimeStamp, Status) -> + US = {LUser, LServer}, + {atomic, ejabberd_riak:put(#last_activity{us = US, + timestamp = TimeStamp, + status = Status}, + last_activity_schema())}. + +remove_user(LUser, LServer) -> + {atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}. + +import(_LServer, #last_activity{} = LA) -> + ejabberd_riak:put(LA, last_activity_schema()). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +last_activity_schema() -> + {record_info(fields, last_activity), #last_activity{}}. diff --git a/src/mod_last_sql.erl b/src/mod_last_sql.erl new file mode 100644 index 000000000..edfa37639 --- /dev/null +++ b/src/mod_last_sql.erl @@ -0,0 +1,75 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_last_sql). +-behaviour(mod_last). + +%% API +-export([init/2, get_last/2, store_last_info/4, remove_user/2, + import/1, import/2, export/1]). + +-include("mod_last.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +get_last(LUser, LServer) -> + case catch odbc_queries:get_last(LServer, LUser) of + {selected, []} -> + not_found; + {selected, [{TimeStamp, Status}]} -> + {ok, TimeStamp, Status}; + Reason -> + ?ERROR_MSG("failed to get last for user ~s@~s: ~p", + [LUser, LServer, Reason]), + {error, {invalid_result, Reason}} + end. + +store_last_info(LUser, LServer, TimeStamp, Status) -> + odbc_queries:set_last_t(LServer, LUser, TimeStamp, Status). + +remove_user(LUser, LServer) -> + odbc_queries:del_last(LServer, LUser). + +import(_LServer, _LA) -> + pass. + +export(_Server) -> + [{last_activity, + fun(Host, #last_activity{us = {LUser, LServer}, + timestamp = TimeStamp, status = Status}) + when LServer == Host -> + Username = ejabberd_odbc:escape(LUser), + Seconds = + ejabberd_odbc:escape(jlib:integer_to_binary(TimeStamp)), + State = ejabberd_odbc:escape(Status), + [[<<"delete from last where username='">>, Username, <<"';">>], + [<<"insert into last(username, seconds, " + "state) values ('">>, + Username, <<"', '">>, Seconds, <<"', '">>, State, + <<"');">>]]; + (_Host, _R) -> + [] + end}]. + +import(LServer) -> + [{<<"select username, seconds, state from last">>, + fun([LUser, TimeStamp, State]) -> + #last_activity{us = {LUser, LServer}, + timestamp = jlib:binary_to_integer( + TimeStamp), + status = State} + end}]. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== From 2d7e03f5e1ee09f006b206a18365ae07b855c875 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 13 Apr 2016 11:06:59 +0300 Subject: [PATCH 03/15] Clean mod_vcard_xupdate.erl from DB specific code --- include/mod_vcard_xupdate.hrl | 2 + src/mod_vcard_xupdate.erl | 143 +++++-------------------------- src/mod_vcard_xupdate_mnesia.erl | 69 +++++++++++++++ src/mod_vcard_xupdate_riak.erl | 44 ++++++++++ src/mod_vcard_xupdate_sql.erl | 79 +++++++++++++++++ 5 files changed, 216 insertions(+), 121 deletions(-) create mode 100644 include/mod_vcard_xupdate.hrl create mode 100644 src/mod_vcard_xupdate_mnesia.erl create mode 100644 src/mod_vcard_xupdate_riak.erl create mode 100644 src/mod_vcard_xupdate_sql.erl diff --git a/include/mod_vcard_xupdate.hrl b/include/mod_vcard_xupdate.hrl new file mode 100644 index 000000000..8634597aa --- /dev/null +++ b/include/mod_vcard_xupdate.hrl @@ -0,0 +1,2 @@ +-record(vcard_xupdate, {us = {<<>>, <<>>} :: {binary(), binary()}, + hash = <<>> :: binary()}). diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl index 18fb09a58..198312c36 100644 --- a/src/mod_vcard_xupdate.erl +++ b/src/mod_vcard_xupdate.erl @@ -17,26 +17,22 @@ -include("ejabberd.hrl"). -include("logger.hrl"). - +-include("mod_vcard_xupdate.hrl"). -include("jlib.hrl"). --record(vcard_xupdate, {us = {<<>>, <<>>} :: {binary(), binary()}, - hash = <<>> :: binary()}). +-callback init(binary(), gen_mod:opts()) -> any(). +-callback import(binary(), #vcard_xupdate{}) -> ok | pass. +-callback add_xupdate(binary(), binary(), binary()) -> {atomic, any()}. +-callback get_xupdate(binary(), binary()) -> binary() | undefined. +-callback remove_xupdate(binary(), binary()) -> {atomic, any()}. %%==================================================================== %% gen_mod callbacks %%==================================================================== start(Host, Opts) -> - case gen_mod:db_type(Host, Opts) of - mnesia -> - mnesia:create_table(vcard_xupdate, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, vcard_xupdate)}]), - update_table(); - _ -> ok - end, + Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod:init(Host, Opts), ejabberd_hooks:add(c2s_update_presence, Host, ?MODULE, update_presence, 100), ejabberd_hooks:add(vcard_set, Host, ?MODULE, vcard_set, @@ -79,75 +75,16 @@ vcard_set(LUser, LServer, VCARD) -> %%==================================================================== add_xupdate(LUser, LServer, Hash) -> - add_xupdate(LUser, LServer, Hash, - gen_mod:db_type(LServer, ?MODULE)). - -add_xupdate(LUser, LServer, Hash, mnesia) -> - F = fun () -> - mnesia:write(#vcard_xupdate{us = {LUser, LServer}, - hash = Hash}) - end, - mnesia:transaction(F); -add_xupdate(LUser, LServer, Hash, riak) -> - {atomic, ejabberd_riak:put(#vcard_xupdate{us = {LUser, LServer}, - hash = Hash}, - vcard_xupdate_schema())}; -add_xupdate(LUser, LServer, Hash, odbc) -> - Username = ejabberd_odbc:escape(LUser), - SHash = ejabberd_odbc:escape(Hash), - F = fun () -> - odbc_queries:update_t(<<"vcard_xupdate">>, - [<<"username">>, <<"hash">>], - [Username, SHash], - [<<"username='">>, Username, <<"'">>]) - end, - ejabberd_odbc:sql_transaction(LServer, F). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:add_xupdate(LUser, LServer, Hash). get_xupdate(LUser, LServer) -> - get_xupdate(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). - -get_xupdate(LUser, LServer, mnesia) -> - case mnesia:dirty_read(vcard_xupdate, {LUser, LServer}) - of - [#vcard_xupdate{hash = Hash}] -> Hash; - _ -> undefined - end; -get_xupdate(LUser, LServer, riak) -> - case ejabberd_riak:get(vcard_xupdate, vcard_xupdate_schema(), - {LUser, LServer}) of - {ok, #vcard_xupdate{hash = Hash}} -> Hash; - _ -> undefined - end; -get_xupdate(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - case ejabberd_odbc:sql_query(LServer, - [<<"select hash from vcard_xupdate where " - "username='">>, - Username, <<"';">>]) - of - {selected, [<<"hash">>], [[Hash]]} -> Hash; - _ -> undefined - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:get_xupdate(LUser, LServer). remove_xupdate(LUser, LServer) -> - remove_xupdate(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). - -remove_xupdate(LUser, LServer, mnesia) -> - F = fun () -> - mnesia:delete({vcard_xupdate, {LUser, LServer}}) - end, - mnesia:transaction(F); -remove_xupdate(LUser, LServer, riak) -> - {atomic, ejabberd_riak:delete(vcard_xupdate, {LUser, LServer})}; -remove_xupdate(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - F = fun () -> - ejabberd_odbc:sql_query_t([<<"delete from vcard_xupdate where username='">>, - Username, <<"';">>]) - end, - ejabberd_odbc:sql_transaction(LServer, F). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:remove_xupdate(LUser, LServer). %%%---------------------------------------------------------------------- %%% Presence stanza rebuilding @@ -184,53 +121,17 @@ build_xphotoel(User, Host) -> attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}], children = PhotoEl}. -vcard_xupdate_schema() -> - {record_info(fields, vcard_xupdate), #vcard_xupdate{}}. - -update_table() -> - Fields = record_info(fields, vcard_xupdate), - case mnesia:table_info(vcard_xupdate, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - vcard_xupdate, Fields, set, - fun(#vcard_xupdate{us = {U, _}}) -> U end, - fun(#vcard_xupdate{us = {U, S}, hash = Hash} = R) -> - R#vcard_xupdate{us = {iolist_to_binary(U), - iolist_to_binary(S)}, - hash = iolist_to_binary(Hash)} - end); - _ -> - ?INFO_MSG("Recreating vcard_xupdate table", []), - mnesia:transform_table(vcard_xupdate, ignore, Fields) - end. - -export(_Server) -> - [{vcard_xupdate, - fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash}) - when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - SHash = ejabberd_odbc:escape(Hash), - [[<<"delete from vcard_xupdate where username='">>, - Username, <<"';">>], - [<<"insert into vcard_xupdate(username, " - "hash) values ('">>, - Username, <<"', '">>, SHash, <<"');">>]]; - (_Host, _R) -> - [] - end}]. +export(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:export(LServer). import(LServer) -> - [{<<"select username, hash from vcard_xupdate;">>, - fun([LUser, Hash]) -> - #vcard_xupdate{us = {LUser, LServer}, hash = Hash} - end}]. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:import(LServer). -import(_LServer, mnesia, #vcard_xupdate{} = R) -> - mnesia:dirty_write(R); -import(_LServer, riak, #vcard_xupdate{} = R) -> - ejabberd_riak:put(R, vcard_xupdate_schema()); -import(_, _, _) -> - pass. +import(LServer, DBType, LA) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, LA). mod_opt_type(db_type) -> fun gen_mod:v_db/1; mod_opt_type(_) -> [db_type]. diff --git a/src/mod_vcard_xupdate_mnesia.erl b/src/mod_vcard_xupdate_mnesia.erl new file mode 100644 index 000000000..f1b1693e4 --- /dev/null +++ b/src/mod_vcard_xupdate_mnesia.erl @@ -0,0 +1,69 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_vcard_xupdate_mnesia). +-behaviour(mod_vcard_xupdate). + +%% API +-export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2]). + +-include("mod_vcard_xupdate.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + mnesia:create_table(vcard_xupdate, + [{disc_copies, [node()]}, + {attributes, + record_info(fields, vcard_xupdate)}]), + update_table(). + +add_xupdate(LUser, LServer, Hash) -> + F = fun () -> + mnesia:write(#vcard_xupdate{us = {LUser, LServer}, + hash = Hash}) + end, + mnesia:transaction(F). + +get_xupdate(LUser, LServer) -> + case mnesia:dirty_read(vcard_xupdate, {LUser, LServer}) + of + [#vcard_xupdate{hash = Hash}] -> Hash; + _ -> undefined + end. + +remove_xupdate(LUser, LServer) -> + F = fun () -> + mnesia:delete({vcard_xupdate, {LUser, LServer}}) + end, + mnesia:transaction(F). + +import(_LServer, #vcard_xupdate{} = R) -> + mnesia:dirty_write(R). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +update_table() -> + Fields = record_info(fields, vcard_xupdate), + case mnesia:table_info(vcard_xupdate, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + vcard_xupdate, Fields, set, + fun(#vcard_xupdate{us = {U, _}}) -> U end, + fun(#vcard_xupdate{us = {U, S}, hash = Hash} = R) -> + R#vcard_xupdate{us = {iolist_to_binary(U), + iolist_to_binary(S)}, + hash = iolist_to_binary(Hash)} + end); + _ -> + ?INFO_MSG("Recreating vcard_xupdate table", []), + mnesia:transform_table(vcard_xupdate, ignore, Fields) + end. diff --git a/src/mod_vcard_xupdate_riak.erl b/src/mod_vcard_xupdate_riak.erl new file mode 100644 index 000000000..129a0c6a2 --- /dev/null +++ b/src/mod_vcard_xupdate_riak.erl @@ -0,0 +1,44 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_vcard_xupdate_riak). + +%% API +-export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2]). + +-include("mod_vcard_xupdate.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +add_xupdate(LUser, LServer, Hash) -> + {atomic, ejabberd_riak:put(#vcard_xupdate{us = {LUser, LServer}, + hash = Hash}, + vcard_xupdate_schema())}. + +get_xupdate(LUser, LServer) -> + case ejabberd_riak:get(vcard_xupdate, vcard_xupdate_schema(), + {LUser, LServer}) of + {ok, #vcard_xupdate{hash = Hash}} -> Hash; + _ -> undefined + end. + +remove_xupdate(LUser, LServer) -> + {atomic, ejabberd_riak:delete(vcard_xupdate, {LUser, LServer})}. + +import(_LServer, #vcard_xupdate{} = R) -> + ejabberd_riak:put(R, vcard_xupdate_schema()). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +vcard_xupdate_schema() -> + {record_info(fields, vcard_xupdate), #vcard_xupdate{}}. diff --git a/src/mod_vcard_xupdate_sql.erl b/src/mod_vcard_xupdate_sql.erl new file mode 100644 index 000000000..d580b1515 --- /dev/null +++ b/src/mod_vcard_xupdate_sql.erl @@ -0,0 +1,79 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_vcard_xupdate_sql). + +%% API +-export([init/2, import/2, add_xupdate/3, get_xupdate/2, remove_xupdate/2, + import/1, export/1]). + +-include("mod_vcard_xupdate.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +add_xupdate(LUser, LServer, Hash) -> + Username = ejabberd_odbc:escape(LUser), + SHash = ejabberd_odbc:escape(Hash), + F = fun () -> + odbc_queries:update_t(<<"vcard_xupdate">>, + [<<"username">>, <<"hash">>], + [Username, SHash], + [<<"username='">>, Username, <<"'">>]) + end, + ejabberd_odbc:sql_transaction(LServer, F). + +get_xupdate(LUser, LServer) -> + Username = ejabberd_odbc:escape(LUser), + case ejabberd_odbc:sql_query(LServer, + [<<"select hash from vcard_xupdate where " + "username='">>, + Username, <<"';">>]) + of + {selected, [<<"hash">>], [[Hash]]} -> Hash; + _ -> undefined + end. + +remove_xupdate(LUser, LServer) -> + Username = ejabberd_odbc:escape(LUser), + F = fun () -> + ejabberd_odbc:sql_query_t([<<"delete from vcard_xupdate where username='">>, + Username, <<"';">>]) + end, + ejabberd_odbc:sql_transaction(LServer, F). + +export(_Server) -> + [{vcard_xupdate, + fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash}) + when LServer == Host -> + Username = ejabberd_odbc:escape(LUser), + SHash = ejabberd_odbc:escape(Hash), + [[<<"delete from vcard_xupdate where username='">>, + Username, <<"';">>], + [<<"insert into vcard_xupdate(username, " + "hash) values ('">>, + Username, <<"', '">>, SHash, <<"');">>]]; + (_Host, _R) -> + [] + end}]. + +import(LServer) -> + [{<<"select username, hash from vcard_xupdate;">>, + fun([LUser, Hash]) -> + #vcard_xupdate{us = {LUser, LServer}, hash = Hash} + end}]. + +import(_LServer, _) -> + pass. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== From cd094bc903a01428463e355968432f0510b28f84 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 13 Apr 2016 11:41:04 +0300 Subject: [PATCH 04/15] Clean mod_caps.erl from DB specific code --- include/mod_caps.hrl | 4 ++ src/mod_caps.erl | 140 +++++----------------------------------- src/mod_caps_mnesia.erl | 73 +++++++++++++++++++++ src/mod_caps_riak.erl | 38 +++++++++++ src/mod_caps_sql.erl | 71 ++++++++++++++++++++ 5 files changed, 203 insertions(+), 123 deletions(-) create mode 100644 include/mod_caps.hrl create mode 100644 src/mod_caps_mnesia.erl create mode 100644 src/mod_caps_riak.erl create mode 100644 src/mod_caps_sql.erl diff --git a/include/mod_caps.hrl b/include/mod_caps.hrl new file mode 100644 index 000000000..067df9490 --- /dev/null +++ b/include/mod_caps.hrl @@ -0,0 +1,4 @@ +-record(caps_features, + {node_pair = {<<"">>, <<"">>} :: {binary(), binary()}, + features = [] :: [binary()] | pos_integer() + }). diff --git a/src/mod_caps.erl b/src/mod_caps.erl index 0646d3812..bd0f14f5e 100644 --- a/src/mod_caps.erl +++ b/src/mod_caps.erl @@ -80,6 +80,12 @@ -record(state, {host = <<"">> :: binary()}). +-callback init(binary(), gen_mod:opts()) -> any(). +-callback caps_read(binary(), {binary(), binary()}) -> + {ok, non_neg_integer() | [binary()]} | error. +-callback caps_write(binary(), {binary(), binary()}, + non_neg_integer() | [binary()]) -> any(). + start_link(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), gen_server:start_link({local, Proc}, ?MODULE, @@ -300,28 +306,9 @@ c2s_broadcast_recipients(InAcc, Host, C2SState, end; c2s_broadcast_recipients(Acc, _, _, _, _, _) -> Acc. -init_db(mnesia, _Host) -> - case catch mnesia:table_info(caps_features, storage_type) of - {'EXIT', _} -> - ok; - disc_only_copies -> - ok; - _ -> - mnesia:delete_table(caps_features) - end, - mnesia:create_table(caps_features, - [{disc_only_copies, [node()]}, - {local_content, true}, - {attributes, - record_info(fields, caps_features)}]), - update_table(), - mnesia:add_table_copy(caps_features, node(), - disc_only_copies); -init_db(_, _) -> - ok. - init([Host, Opts]) -> - init_db(gen_mod:db_type(Host, Opts), Host), + Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod:init(Host, Opts), MaxSize = gen_mod:get_opt(cache_size, Opts, fun(I) when is_integer(I), I>0 -> I end, 1000), @@ -450,65 +437,13 @@ feature_response(_IQResult, Host, From, Caps, caps_read_fun(Host, Node) -> LServer = jid:nameprep(Host), - DBType = gen_mod:db_type(LServer, ?MODULE), - caps_read_fun(LServer, Node, DBType). - -caps_read_fun(_LServer, Node, mnesia) -> - fun () -> - case mnesia:dirty_read({caps_features, Node}) of - [#caps_features{features = Features}] -> {ok, Features}; - _ -> error - end - end; -caps_read_fun(_LServer, Node, riak) -> - fun() -> - case ejabberd_riak:get(caps_features, caps_features_schema(), Node) of - {ok, #caps_features{features = Features}} -> {ok, Features}; - _ -> error - end - end; -caps_read_fun(LServer, {Node, SubNode}, odbc) -> - fun() -> - SNode = ejabberd_odbc:escape(Node), - SSubNode = ejabberd_odbc:escape(SubNode), - case ejabberd_odbc:sql_query( - LServer, [<<"select feature from caps_features where ">>, - <<"node='">>, SNode, <<"' and subnode='">>, - SSubNode, <<"';">>]) of - {selected, [<<"feature">>], [[H]|_] = Fs} -> - case catch jlib:binary_to_integer(H) of - Int when is_integer(Int), Int>=0 -> - {ok, Int}; - _ -> - {ok, lists:flatten(Fs)} - end; - _ -> - error - end - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + fun() -> Mod:caps_read(LServer, Node) end. caps_write_fun(Host, Node, Features) -> LServer = jid:nameprep(Host), - DBType = gen_mod:db_type(LServer, ?MODULE), - caps_write_fun(LServer, Node, Features, DBType). - -caps_write_fun(_LServer, Node, Features, mnesia) -> - fun () -> - mnesia:dirty_write(#caps_features{node_pair = Node, - features = Features}) - end; -caps_write_fun(_LServer, Node, Features, riak) -> - fun () -> - ejabberd_riak:put(#caps_features{node_pair = Node, - features = Features}, - caps_features_schema()) - end; -caps_write_fun(LServer, NodePair, Features, odbc) -> - fun () -> - ejabberd_odbc:sql_transaction( - LServer, - sql_write_features_t(NodePair, Features)) - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + fun() -> Mod:caps_write(LServer, Node, Features) end. make_my_disco_hash(Host) -> JID = jid:make(<<"">>, Host, <<"">>), @@ -658,61 +593,20 @@ is_valid_node(Node) -> false end. -update_table() -> - Fields = record_info(fields, caps_features), - case mnesia:table_info(caps_features, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - caps_features, Fields, set, - fun(#caps_features{node_pair = {N, _}}) -> N end, - fun(#caps_features{node_pair = {N, P}, - features = Fs} = R) -> - NewFs = if is_integer(Fs) -> - Fs; - true -> - [iolist_to_binary(F) || F <- Fs] - end, - R#caps_features{node_pair = {iolist_to_binary(N), - iolist_to_binary(P)}, - features = NewFs} - end); - _ -> - ?INFO_MSG("Recreating caps_features table", []), - mnesia:transform_table(caps_features, ignore, Fields) - end. - -sql_write_features_t({Node, SubNode}, Features) -> - SNode = ejabberd_odbc:escape(Node), - SSubNode = ejabberd_odbc:escape(SubNode), - NewFeatures = if is_integer(Features) -> - [jlib:integer_to_binary(Features)]; - true -> - Features - end, - [[<<"delete from caps_features where node='">>, - SNode, <<"' and subnode='">>, SSubNode, <<"';">>]| - [[<<"insert into caps_features(node, subnode, feature) ">>, - <<"values ('">>, SNode, <<"', '">>, SSubNode, <<"', '">>, - ejabberd_odbc:escape(F), <<"');">>] || F <- NewFeatures]]. - caps_features_schema() -> {record_info(fields, caps_features), #caps_features{}}. -export(_Server) -> - [{caps_features, - fun(_Host, #caps_features{node_pair = NodePair, - features = Features}) -> - sql_write_features_t(NodePair, Features); - (_Host, _R) -> - [] - end}]. +export(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:export(LServer). import_info() -> [{<<"caps_features">>, 4}]. import_start(LServer, DBType) -> ets:new(caps_features_tmp, [private, named_table, bag]), - init_db(DBType, LServer), + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:init(LServer, []), ok. import(_LServer, {odbc, _}, _DBType, <<"caps_features">>, diff --git a/src/mod_caps_mnesia.erl b/src/mod_caps_mnesia.erl new file mode 100644 index 000000000..0bf04b2c3 --- /dev/null +++ b/src/mod_caps_mnesia.erl @@ -0,0 +1,73 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_caps_mnesia). +-behaviour(mod_caps). + +%% API +-export([init/2, caps_read/2, caps_write/3]). + +-include("mod_caps.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + case catch mnesia:table_info(caps_features, storage_type) of + {'EXIT', _} -> + ok; + disc_only_copies -> + ok; + _ -> + mnesia:delete_table(caps_features) + end, + mnesia:create_table(caps_features, + [{disc_only_copies, [node()]}, + {local_content, true}, + {attributes, + record_info(fields, caps_features)}]), + update_table(), + mnesia:add_table_copy(caps_features, node(), + disc_only_copies). + +caps_read(_LServer, Node) -> + case mnesia:dirty_read({caps_features, Node}) of + [#caps_features{features = Features}] -> {ok, Features}; + _ -> error + end. + +caps_write(_LServer, Node, Features) -> + mnesia:dirty_write(#caps_features{node_pair = Node, + features = Features}). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +update_table() -> + Fields = record_info(fields, caps_features), + case mnesia:table_info(caps_features, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + caps_features, Fields, set, + fun(#caps_features{node_pair = {N, _}}) -> N end, + fun(#caps_features{node_pair = {N, P}, + features = Fs} = R) -> + NewFs = if is_integer(Fs) -> + Fs; + true -> + [iolist_to_binary(F) || F <- Fs] + end, + R#caps_features{node_pair = {iolist_to_binary(N), + iolist_to_binary(P)}, + features = NewFs} + end); + _ -> + ?INFO_MSG("Recreating caps_features table", []), + mnesia:transform_table(caps_features, ignore, Fields) + end. diff --git a/src/mod_caps_riak.erl b/src/mod_caps_riak.erl new file mode 100644 index 000000000..6e59ba867 --- /dev/null +++ b/src/mod_caps_riak.erl @@ -0,0 +1,38 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_caps_riak). +-behaviour(mod_caps). + +%% API +-export([init/2, caps_read/2, caps_write/3]). + +-include("mod_caps.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +caps_read(_LServer, Node) -> + case ejabberd_riak:get(caps_features, caps_features_schema(), Node) of + {ok, #caps_features{features = Features}} -> {ok, Features}; + _ -> error + end. + +caps_write(_LServer, Node, Features) -> + ejabberd_riak:put(#caps_features{node_pair = Node, + features = Features}, + caps_features_schema()). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +caps_features_schema() -> + {record_info(fields, caps_features), #caps_features{}}. diff --git a/src/mod_caps_sql.erl b/src/mod_caps_sql.erl new file mode 100644 index 000000000..353b95b1b --- /dev/null +++ b/src/mod_caps_sql.erl @@ -0,0 +1,71 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_caps_sql). +-behaviour(mod_caps). + +%% API +-export([init/2, caps_read/2, caps_write/3, export/1]). + +-include("mod_caps.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +caps_read(LServer, {Node, SubNode}) -> + SNode = ejabberd_odbc:escape(Node), + SSubNode = ejabberd_odbc:escape(SubNode), + case ejabberd_odbc:sql_query( + LServer, [<<"select feature from caps_features where ">>, + <<"node='">>, SNode, <<"' and subnode='">>, + SSubNode, <<"';">>]) of + {selected, [<<"feature">>], [[H]|_] = Fs} -> + case catch jlib:binary_to_integer(H) of + Int when is_integer(Int), Int>=0 -> + {ok, Int}; + _ -> + {ok, lists:flatten(Fs)} + end; + _ -> + error + end. + +caps_write(LServer, NodePair, Features) -> + ejabberd_odbc:sql_transaction( + LServer, + sql_write_features_t(NodePair, Features)). + +export(_Server) -> + [{caps_features, + fun(_Host, #caps_features{node_pair = NodePair, + features = Features}) -> + sql_write_features_t(NodePair, Features); + (_Host, _R) -> + [] + end}]. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +sql_write_features_t({Node, SubNode}, Features) -> + SNode = ejabberd_odbc:escape(Node), + SSubNode = ejabberd_odbc:escape(SubNode), + NewFeatures = if is_integer(Features) -> + [jlib:integer_to_binary(Features)]; + true -> + Features + end, + [[<<"delete from caps_features where node='">>, + SNode, <<"' and subnode='">>, SSubNode, <<"';">>]| + [[<<"insert into caps_features(node, subnode, feature) ">>, + <<"values ('">>, SNode, <<"', '">>, SSubNode, <<"', '">>, + ejabberd_odbc:escape(F), <<"');">>] || F <- NewFeatures]]. + From b5d1ce795f798af7c98d650b14b95cda1d2e6eb0 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 13 Apr 2016 13:04:04 +0300 Subject: [PATCH 05/15] Clean mod_announce.erl from DB specific code --- include/mod_announce.hrl | 5 + src/mod_announce.erl | 324 +++++------------------------------- src/mod_announce_mnesia.erl | 129 ++++++++++++++ src/mod_announce_riak.erl | 87 ++++++++++ src/mod_announce_sql.erl | 132 +++++++++++++++ 5 files changed, 391 insertions(+), 286 deletions(-) create mode 100644 include/mod_announce.hrl create mode 100644 src/mod_announce_mnesia.erl create mode 100644 src/mod_announce_riak.erl create mode 100644 src/mod_announce_sql.erl diff --git a/include/mod_announce.hrl b/include/mod_announce.hrl new file mode 100644 index 000000000..83d72aaf1 --- /dev/null +++ b/include/mod_announce.hrl @@ -0,0 +1,5 @@ +-record(motd, {server = <<"">> :: binary(), + packet = #xmlel{} :: xmlel()}). + +-record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1', + dummy = [] :: [] | '_'}). diff --git a/src/mod_announce.erl b/src/mod_announce.erl index 0cf8c5349..d7251c50b 100644 --- a/src/mod_announce.erl +++ b/src/mod_announce.erl @@ -41,11 +41,16 @@ -include("logger.hrl"). -include("jlib.hrl"). -include("adhoc.hrl"). +-include("mod_announce.hrl"). --record(motd, {server = <<"">> :: binary(), - packet = #xmlel{} :: xmlel()}). --record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1', - dummy = [] :: [] | '_'}). +-callback init(binary(), gen_mod:opts()) -> any(). +-callback import(binary(), #motd{} | #motd_users{}) -> ok | pass. +-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> {atomic, any()}. +-callback set_motd(binary(), xmlel()) -> {atomic, any()}. +-callback delete_motd(binary()) -> {atomic, any()}. +-callback get_motd(binary()) -> {ok, xmlel()} | error. +-callback is_motd_user(binary(), binary()) -> boolean(). +-callback set_motd_user(binary(), binary()) -> {atomic, any()}. -define(PROCNAME, ejabberd_announce). @@ -55,20 +60,8 @@ tokenize(Node) -> str:tokens(Node, <<"/#">>). start(Host, Opts) -> - case gen_mod:db_type(Host, Opts) of - mnesia -> - mnesia:create_table(motd, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, motd)}]), - mnesia:create_table(motd_users, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, motd_users)}]), - update_tables(); - _ -> - ok - end, + Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod:init(Host, Opts), ejabberd_hooks:add(local_send_to_resource_hook, Host, ?MODULE, announce, 50), ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50), @@ -789,41 +782,8 @@ announce_motd(Host, Packet) -> announce_motd_update(LServer, Packet), Sessions = ejabberd_sm:get_vh_session_list(LServer), announce_online1(Sessions, LServer, Packet), - case gen_mod:db_type(LServer, ?MODULE) of - mnesia -> - F = fun() -> - lists:foreach( - fun({U, S, _R}) -> - mnesia:write(#motd_users{us = {U, S}}) - end, Sessions) - end, - mnesia:transaction(F); - riak -> - try - lists:foreach( - fun({U, S, _R}) -> - ok = ejabberd_riak:put(#motd_users{us = {U, S}}, - motd_users_schema(), - [{'2i', [{<<"server">>, S}]}]) - end, Sessions), - {atomic, ok} - catch _:{badmatch, Err} -> - {atomic, Err} - end; - odbc -> - F = fun() -> - lists:foreach( - fun({U, _S, _R}) -> - Username = ejabberd_odbc:escape(U), - odbc_queries:update_t( - <<"motd">>, - [<<"username">>, <<"xml">>], - [Username, <<"">>], - [<<"username='">>, Username, <<"'">>]) - end, Sessions) - end, - ejabberd_odbc:sql_transaction(LServer, F) - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:set_motd_users(LServer, Sessions). announce_motd_update(From, To, Packet) -> Host = To#jid.lserver, @@ -853,27 +813,8 @@ announce_all_hosts_motd_update(From, To, Packet) -> announce_motd_update(LServer, Packet) -> announce_motd_delete(LServer), - case gen_mod:db_type(LServer, ?MODULE) of - mnesia -> - F = fun() -> - mnesia:write(#motd{server = LServer, packet = Packet}) - end, - mnesia:transaction(F); - riak -> - {atomic, ejabberd_riak:put(#motd{server = LServer, - packet = Packet}, - motd_schema())}; - odbc -> - XML = ejabberd_odbc:escape(fxml:element_to_binary(Packet)), - F = fun() -> - odbc_queries:update_t( - <<"motd">>, - [<<"username">>, <<"xml">>], - [<<"">>, XML], - [<<"username=''">>]) - end, - ejabberd_odbc:sql_transaction(LServer, F) - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:set_motd(LServer, Packet). announce_motd_delete(From, To, Packet) -> Host = To#jid.lserver, @@ -902,112 +843,30 @@ announce_all_hosts_motd_delete(From, To, Packet) -> end. announce_motd_delete(LServer) -> - case gen_mod:db_type(LServer, ?MODULE) of - mnesia -> - F = fun() -> - mnesia:delete({motd, LServer}), - mnesia:write_lock_table(motd_users), - Users = mnesia:select( - motd_users, - [{#motd_users{us = '$1', _ = '_'}, - [{'==', {element, 2, '$1'}, LServer}], - ['$1']}]), - lists:foreach(fun(US) -> - mnesia:delete({motd_users, US}) - end, Users) - end, - mnesia:transaction(F); - riak -> - try - ok = ejabberd_riak:delete(motd, LServer), - ok = ejabberd_riak:delete_by_index(motd_users, - <<"server">>, - LServer), - {atomic, ok} - catch _:{badmatch, Err} -> - {atomic, Err} - end; - odbc -> - F = fun() -> - ejabberd_odbc:sql_query_t([<<"delete from motd;">>]) - end, - ejabberd_odbc:sql_transaction(LServer, F) - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:delete_motd(LServer). -send_motd(JID) -> - send_motd(JID, gen_mod:db_type(JID#jid.lserver, ?MODULE)). - -send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) -> - case catch mnesia:dirty_read({motd, LServer}) of - [#motd{packet = Packet}] -> - US = {LUser, LServer}, - case catch mnesia:dirty_read({motd_users, US}) of - [#motd_users{}] -> - ok; - _ -> +send_motd(#jid{luser = LUser, lserver = LServer} = JID) when LUser /= <<>> -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + case Mod:get_motd(LServer) of + {ok, Packet} -> + case Mod:is_motd_user(LUser, LServer) of + false -> Local = jid:make(<<>>, LServer, <<>>), ejabberd_router:route(Local, JID, Packet), - F = fun() -> - mnesia:write(#motd_users{us = US}) - end, - mnesia:transaction(F) + Mod:set_motd_user(LUser, LServer); + true -> + ok end; - _ -> + error -> ok end; -send_motd(#jid{luser = LUser, lserver = LServer} = JID, riak) -> - case catch ejabberd_riak:get(motd, motd_schema(), LServer) of - {ok, #motd{packet = Packet}} -> - US = {LUser, LServer}, - case ejabberd_riak:get(motd_users, motd_users_schema(), US) of - {ok, #motd_users{}} -> - ok; - _ -> - Local = jid:make(<<>>, LServer, <<>>), - ejabberd_router:route(Local, JID, Packet), - {atomic, ejabberd_riak:put( - #motd_users{us = US}, motd_users_schema(), - [{'2i', [{<<"server">>, LServer}]}])} - end; - _ -> - ok - end; -send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= <<>> -> - case catch ejabberd_odbc:sql_query( - LServer, [<<"select xml from motd where username='';">>]) of - {selected, [<<"xml">>], [[XML]]} -> - case fxml_stream:parse_element(XML) of - {error, _} -> - ok; - Packet -> - Username = ejabberd_odbc:escape(LUser), - case catch ejabberd_odbc:sql_query( - LServer, - [<<"select username from motd " - "where username='">>, Username, <<"';">>]) of - {selected, [<<"username">>], []} -> - Local = jid:make(<<"">>, LServer, <<"">>), - ejabberd_router:route(Local, JID, Packet), - F = fun() -> - odbc_queries:update_t( - <<"motd">>, - [<<"username">>, <<"xml">>], - [Username, <<"">>], - [<<"username='">>, Username, <<"'">>]) - end, - ejabberd_odbc:sql_transaction(LServer, F); - _ -> - ok - end - end; - _ -> - ok - end; -send_motd(_, odbc) -> +send_motd(_) -> ok. get_stored_motd(LServer) -> - case get_stored_motd_packet(LServer, gen_mod:db_type(LServer, ?MODULE)) of + Mod = gen_mod:db_mod(LServer, ?MODULE), + case Mod:get_motd(LServer) of {ok, Packet} -> {fxml:get_subtag_cdata(Packet, <<"subject">>), fxml:get_subtag_cdata(Packet, <<"body">>)}; @@ -1015,34 +874,6 @@ get_stored_motd(LServer) -> {<<>>, <<>>} end. -get_stored_motd_packet(LServer, mnesia) -> - case catch mnesia:dirty_read({motd, LServer}) of - [#motd{packet = Packet}] -> - {ok, Packet}; - _ -> - error - end; -get_stored_motd_packet(LServer, riak) -> - case ejabberd_riak:get(motd, motd_schema(), LServer) of - {ok, #motd{packet = Packet}} -> - {ok, Packet}; - _ -> - error - end; -get_stored_motd_packet(LServer, odbc) -> - case catch ejabberd_odbc:sql_query( - LServer, [<<"select xml from motd where username='';">>]) of - {selected, [<<"xml">>], [[XML]]} -> - case fxml_stream:parse_element(XML) of - {error, _} -> - error; - Packet -> - {ok, Packet} - end; - _ -> - error - end. - %% This function is similar to others, but doesn't perform any ACL verification send_announcement_to_all(Host, SubjectS, BodyS) -> SubjectEls = if SubjectS /= <<>> -> @@ -1076,96 +907,17 @@ get_access(Host) -> none). %%------------------------------------------------------------------------- - -update_tables() -> - update_motd_table(), - update_motd_users_table(). - -update_motd_table() -> - Fields = record_info(fields, motd), - case mnesia:table_info(motd, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - motd, Fields, set, - fun(#motd{server = S}) -> S end, - fun(#motd{server = S, packet = P} = R) -> - NewS = iolist_to_binary(S), - NewP = fxml:to_xmlel(P), - R#motd{server = NewS, packet = NewP} - end); - _ -> - ?INFO_MSG("Recreating motd table", []), - mnesia:transform_table(motd, ignore, Fields) - end. - - -update_motd_users_table() -> - Fields = record_info(fields, motd_users), - case mnesia:table_info(motd_users, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - motd_users, Fields, set, - fun(#motd_users{us = {U, _}}) -> U end, - fun(#motd_users{us = {U, S}} = R) -> - NewUS = {iolist_to_binary(U), - iolist_to_binary(S)}, - R#motd_users{us = NewUS} - end); - _ -> - ?INFO_MSG("Recreating motd_users table", []), - mnesia:transform_table(motd_users, ignore, Fields) - end. - -motd_schema() -> - {record_info(fields, motd), #motd{}}. - -motd_users_schema() -> - {record_info(fields, motd_users), #motd_users{}}. - -export(_Server) -> - [{motd, - fun(Host, #motd{server = LServer, packet = El}) - when LServer == Host -> - [[<<"delete from motd where username='';">>], - [<<"insert into motd(username, xml) values ('', '">>, - ejabberd_odbc:escape(fxml:element_to_binary(El)), - <<"');">>]]; - (_Host, _R) -> - [] - end}, - {motd_users, - fun(Host, #motd_users{us = {LUser, LServer}}) - when LServer == Host, LUser /= <<"">> -> - Username = ejabberd_odbc:escape(LUser), - [[<<"delete from motd where username='">>, Username, <<"';">>], - [<<"insert into motd(username, xml) values ('">>, - Username, <<"', '');">>]]; - (_Host, _R) -> - [] - end}]. +export(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:export(LServer). import(LServer) -> - [{<<"select xml from motd where username='';">>, - fun([XML]) -> - El = fxml_stream:parse_element(XML), - #motd{server = LServer, packet = El} - end}, - {<<"select username from motd where xml='';">>, - fun([LUser]) -> - #motd_users{us = {LUser, LServer}} - end}]. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:import(LServer). -import(_LServer, mnesia, #motd{} = Motd) -> - mnesia:dirty_write(Motd); -import(_LServer, mnesia, #motd_users{} = Users) -> - mnesia:dirty_write(Users); -import(_LServer, riak, #motd{} = Motd) -> - ejabberd_riak:put(Motd, motd_schema()); -import(_LServer, riak, #motd_users{us = {_, S}} = Users) -> - ejabberd_riak:put(Users, motd_users_schema(), - [{'2i', [{<<"server">>, S}]}]); -import(_, _, _) -> - pass. +import(LServer, DBType, LA) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, LA). mod_opt_type(access) -> fun (A) when is_atom(A) -> A end; diff --git a/src/mod_announce_mnesia.erl b/src/mod_announce_mnesia.erl new file mode 100644 index 000000000..c43eb853b --- /dev/null +++ b/src/mod_announce_mnesia.erl @@ -0,0 +1,129 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_announce_mnesia). +-behaviour(mod_announce). + +%% API +-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1, + get_motd/1, is_motd_user/2, set_motd_user/2, import/2]). + +-include("jlib.hrl"). +-include("mod_announce.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + mnesia:create_table(motd, + [{disc_copies, [node()]}, + {attributes, + record_info(fields, motd)}]), + mnesia:create_table(motd_users, + [{disc_copies, [node()]}, + {attributes, + record_info(fields, motd_users)}]), + update_tables(). + +set_motd_users(_LServer, USRs) -> + F = fun() -> + lists:foreach( + fun({U, S, _R}) -> + mnesia:write(#motd_users{us = {U, S}}) + end, USRs) + end, + mnesia:transaction(F). + +set_motd(LServer, Packet) -> + F = fun() -> + mnesia:write(#motd{server = LServer, packet = Packet}) + end, + mnesia:transaction(F). + +delete_motd(LServer) -> + F = fun() -> + mnesia:delete({motd, LServer}), + mnesia:write_lock_table(motd_users), + Users = mnesia:select( + motd_users, + [{#motd_users{us = '$1', _ = '_'}, + [{'==', {element, 2, '$1'}, LServer}], + ['$1']}]), + lists:foreach(fun(US) -> + mnesia:delete({motd_users, US}) + end, Users) + end, + mnesia:transaction(F). + +get_motd(LServer) -> + case mnesia:dirty_read({motd, LServer}) of + [#motd{packet = Packet}] -> + {ok, Packet}; + _ -> + error + end. + +is_motd_user(LUser, LServer) -> + case mnesia:dirty_read({motd_users, {LUser, LServer}}) of + [#motd_users{}] -> true; + _ -> false + end. + +set_motd_user(LUser, LServer) -> + F = fun() -> + mnesia:write(#motd_users{us = {LUser, LServer}}) + end, + mnesia:transaction(F). + +import(_LServer, #motd{} = Motd) -> + mnesia:dirty_write(Motd); +import(_LServer, #motd_users{} = Users) -> + mnesia:dirty_write(Users). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +update_tables() -> + update_motd_table(), + update_motd_users_table(). + +update_motd_table() -> + Fields = record_info(fields, motd), + case mnesia:table_info(motd, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + motd, Fields, set, + fun(#motd{server = S}) -> S end, + fun(#motd{server = S, packet = P} = R) -> + NewS = iolist_to_binary(S), + NewP = fxml:to_xmlel(P), + R#motd{server = NewS, packet = NewP} + end); + _ -> + ?INFO_MSG("Recreating motd table", []), + mnesia:transform_table(motd, ignore, Fields) + end. + + +update_motd_users_table() -> + Fields = record_info(fields, motd_users), + case mnesia:table_info(motd_users, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + motd_users, Fields, set, + fun(#motd_users{us = {U, _}}) -> U end, + fun(#motd_users{us = {U, S}} = R) -> + NewUS = {iolist_to_binary(U), + iolist_to_binary(S)}, + R#motd_users{us = NewUS} + end); + _ -> + ?INFO_MSG("Recreating motd_users table", []), + mnesia:transform_table(motd_users, ignore, Fields) + end. diff --git a/src/mod_announce_riak.erl b/src/mod_announce_riak.erl new file mode 100644 index 000000000..7ced0b3ce --- /dev/null +++ b/src/mod_announce_riak.erl @@ -0,0 +1,87 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_announce_riak). +-behaviour(mod_announce). + +%% API +-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1, + get_motd/1, is_motd_user/2, set_motd_user/2, import/2]). + +-include("jlib.hrl"). +-include("mod_announce.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +set_motd_users(_LServer, USRs) -> + try + lists:foreach( + fun({U, S, _R}) -> + ok = ejabberd_riak:put(#motd_users{us = {U, S}}, + motd_users_schema(), + [{'2i', [{<<"server">>, S}]}]) + end, USRs), + {atomic, ok} + catch _:{badmatch, Err} -> + {atomic, Err} + end. + +set_motd(LServer, Packet) -> + {atomic, ejabberd_riak:put(#motd{server = LServer, + packet = Packet}, + motd_schema())}. + +delete_motd(LServer) -> + try + ok = ejabberd_riak:delete(motd, LServer), + ok = ejabberd_riak:delete_by_index(motd_users, + <<"server">>, + LServer), + {atomic, ok} + catch _:{badmatch, Err} -> + {atomic, Err} + end. + +get_motd(LServer) -> + case ejabberd_riak:get(motd, motd_schema(), LServer) of + {ok, #motd{packet = Packet}} -> + {ok, Packet}; + _ -> + error + end. + +is_motd_user(LUser, LServer) -> + case ejabberd_riak:get(motd_users, motd_users_schema(), + {LUser, LServer}) of + {ok, #motd_users{}} -> true; + _ -> false + end. + +set_motd_user(LUser, LServer) -> + {atomic, ejabberd_riak:put( + #motd_users{us = {LUser, LServer}}, motd_users_schema(), + [{'2i', [{<<"server">>, LServer}]}])}. + +import(_LServer, #motd{} = Motd) -> + ejabberd_riak:put(Motd, motd_schema()); +import(_LServer, #motd_users{us = {_, S}} = Users) -> + ejabberd_riak:put(Users, motd_users_schema(), + [{'2i', [{<<"server">>, S}]}]). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +motd_schema() -> + {record_info(fields, motd), #motd{}}. + +motd_users_schema() -> + {record_info(fields, motd_users), #motd_users{}}. diff --git a/src/mod_announce_sql.erl b/src/mod_announce_sql.erl new file mode 100644 index 000000000..cf10ffd85 --- /dev/null +++ b/src/mod_announce_sql.erl @@ -0,0 +1,132 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_announce_sql). +-behaviour(mod_announce). + +%% API +-export([init/2, set_motd_users/2, set_motd/2, delete_motd/1, + get_motd/1, is_motd_user/2, set_motd_user/2, import/1, + import/2, export/1]). + +-include("jlib.hrl"). +-include("mod_announce.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +set_motd_users(LServer, USRs) -> + F = fun() -> + lists:foreach( + fun({U, _S, _R}) -> + Username = ejabberd_odbc:escape(U), + odbc_queries:update_t( + <<"motd">>, + [<<"username">>, <<"xml">>], + [Username, <<"">>], + [<<"username='">>, Username, <<"'">>]) + end, USRs) + end, + ejabberd_odbc:sql_transaction(LServer, F). + +set_motd(LServer, Packet) -> + XML = ejabberd_odbc:escape(fxml:element_to_binary(Packet)), + F = fun() -> + odbc_queries:update_t( + <<"motd">>, + [<<"username">>, <<"xml">>], + [<<"">>, XML], + [<<"username=''">>]) + end, + ejabberd_odbc:sql_transaction(LServer, F). + +delete_motd(LServer) -> + F = fun() -> + ejabberd_odbc:sql_query_t([<<"delete from motd;">>]) + end, + ejabberd_odbc:sql_transaction(LServer, F). + +get_motd(LServer) -> + case catch ejabberd_odbc:sql_query( + LServer, [<<"select xml from motd where username='';">>]) of + {selected, [<<"xml">>], [[XML]]} -> + case fxml_stream:parse_element(XML) of + {error, _} -> + error; + Packet -> + {ok, Packet} + end; + _ -> + error + end. + +is_motd_user(LUser, LServer) -> + Username = ejabberd_odbc:escape(LUser), + case catch ejabberd_odbc:sql_query( + LServer, + [<<"select username from motd " + "where username='">>, Username, <<"';">>]) of + {selected, [<<"username">>], [_|_]} -> + true; + _ -> + false + end. + +set_motd_user(LUser, LServer) -> + Username = ejabberd_odbc:escape(LUser), + F = fun() -> + odbc_queries:update_t( + <<"motd">>, + [<<"username">>, <<"xml">>], + [Username, <<"">>], + [<<"username='">>, Username, <<"'">>]) + end, + ejabberd_odbc:sql_transaction(LServer, F). + +export(_Server) -> + [{motd, + fun(Host, #motd{server = LServer, packet = El}) + when LServer == Host -> + [[<<"delete from motd where username='';">>], + [<<"insert into motd(username, xml) values ('', '">>, + ejabberd_odbc:escape(fxml:element_to_binary(El)), + <<"');">>]]; + (_Host, _R) -> + [] + end}, + {motd_users, + fun(Host, #motd_users{us = {LUser, LServer}}) + when LServer == Host, LUser /= <<"">> -> + Username = ejabberd_odbc:escape(LUser), + [[<<"delete from motd where username='">>, Username, <<"';">>], + [<<"insert into motd(username, xml) values ('">>, + Username, <<"', '');">>]]; + (_Host, _R) -> + [] + end}]. + +import(LServer) -> + [{<<"select xml from motd where username='';">>, + fun([XML]) -> + El = fxml_stream:parse_element(XML), + #motd{server = LServer, packet = El} + end}, + {<<"select username from motd where xml='';">>, + fun([LUser]) -> + #motd_users{us = {LUser, LServer}} + end}]. + +import(_, _) -> + pass. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== From ef70ce65ab9d27e9fb399c1762514c34bdf9f97e Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 13 Apr 2016 14:09:34 +0300 Subject: [PATCH 06/15] Clean mod_private.erl from DB specific code --- include/mod_private.hrl | 4 + src/mod_private.erl | 215 ++++++------------------------------- src/mod_private_mnesia.erl | 97 +++++++++++++++++ src/mod_private_riak.erl | 67 ++++++++++++ src/mod_private_sql.erl | 97 +++++++++++++++++ 5 files changed, 297 insertions(+), 183 deletions(-) create mode 100644 include/mod_private.hrl create mode 100644 src/mod_private_mnesia.erl create mode 100644 src/mod_private_riak.erl create mode 100644 src/mod_private_sql.erl diff --git a/include/mod_private.hrl b/include/mod_private.hrl new file mode 100644 index 000000000..d833af35f --- /dev/null +++ b/include/mod_private.hrl @@ -0,0 +1,4 @@ +-record(private_storage, + {usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() | + '$1' | '_'}, + xml = #xmlel{} :: xmlel() | '_' | '$1'}). diff --git a/src/mod_private.erl b/src/mod_private.erl index b4b064502..029789e63 100644 --- a/src/mod_private.erl +++ b/src/mod_private.erl @@ -39,12 +39,14 @@ -include("logger.hrl"). -include("jlib.hrl"). +-include("mod_private.hrl"). --record(private_storage, - {usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() | - '$1' | '_'}, - xml = #xmlel{} :: xmlel() | '_' | '$1'}). - +-callback init(binary(), gen_mod:opts()) -> any(). +-callback import(binary(), #private_storage{}) -> ok | pass. +-callback set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}. +-callback get_data(binary(), binary(), binary()) -> {ok, xmlel()} | error. +-callback get_all_data(binary(), binary()) -> [xmlel()]. + -define(Xmlel_Query(Attrs, Children), #xmlel{name = <<"query">>, attrs = Attrs, children = Children}). @@ -52,15 +54,8 @@ start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, one_queue), - case gen_mod:db_type(Host, Opts) of - mnesia -> - mnesia:create_table(private_storage, - [{disc_only_copies, [node()]}, - {attributes, - record_info(fields, private_storage)}]), - update_table(); - _ -> ok - end, + Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod:init(Host, Opts), ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, @@ -139,190 +134,44 @@ filter_xmlels([_ | Xmlels], Data) -> filter_xmlels(Xmlels, Data). set_data(LUser, LServer, Data) -> - DBType = gen_mod:db_type(LServer, ?MODULE), - F = fun () -> - lists:foreach(fun (Datum) -> - set_data(LUser, LServer, - Datum, DBType) - end, - Data) - end, - case DBType of - odbc -> ejabberd_odbc:sql_transaction(LServer, F); - mnesia -> mnesia:transaction(F); - riak -> F() - end. - -set_data(LUser, LServer, {XmlNS, Xmlel}, mnesia) -> - mnesia:write(#private_storage{usns = - {LUser, LServer, XmlNS}, - xml = Xmlel}); -set_data(LUser, LServer, {XMLNS, El}, odbc) -> - SData = fxml:element_to_binary(El), - odbc_queries:set_private_data(LServer, LUser, XMLNS, SData); -set_data(LUser, LServer, {XMLNS, El}, riak) -> - ejabberd_riak:put(#private_storage{usns = {LUser, LServer, XMLNS}, - xml = El}, - private_storage_schema(), - [{'2i', [{<<"us">>, {LUser, LServer}}]}]). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:set_data(LUser, LServer, Data). get_data(LUser, LServer, Data) -> - get_data(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE), Data, []). + Mod = gen_mod:db_mod(LServer, ?MODULE), + get_data(LUser, LServer, Data, Mod, []). -get_data(_LUser, _LServer, _DBType, [], - Storage_Xmlels) -> +get_data(_LUser, _LServer, [], _Mod, Storage_Xmlels) -> lists:reverse(Storage_Xmlels); -get_data(LUser, LServer, mnesia, - [{XmlNS, Xmlel} | Data], Storage_Xmlels) -> - case mnesia:dirty_read(private_storage, - {LUser, LServer, XmlNS}) - of - [#private_storage{xml = Storage_Xmlel}] -> - get_data(LUser, LServer, mnesia, Data, - [Storage_Xmlel | Storage_Xmlels]); - _ -> - get_data(LUser, LServer, mnesia, Data, - [Xmlel | Storage_Xmlels]) - end; -get_data(LUser, LServer, odbc, [{XMLNS, El} | Els], - Res) -> - case catch odbc_queries:get_private_data(LServer, - LUser, XMLNS) - of - {selected, [{SData}]} -> - case fxml_stream:parse_element(SData) of - Data when is_record(Data, xmlel) -> - get_data(LUser, LServer, odbc, Els, [Data | Res]) - end; - _ -> get_data(LUser, LServer, odbc, Els, [El | Res]) - end; -get_data(LUser, LServer, riak, [{XMLNS, El} | Els], - Res) -> - case ejabberd_riak:get(private_storage, private_storage_schema(), - {LUser, LServer, XMLNS}) of - {ok, #private_storage{xml = NewEl}} -> - get_data(LUser, LServer, riak, Els, [NewEl|Res]); - _ -> - get_data(LUser, LServer, riak, Els, [El|Res]) +get_data(LUser, LServer, [{XmlNS, Xmlel} | Data], Mod, Storage_Xmlels) -> + case Mod:get_data(LUser, LServer, XmlNS) of + {ok, Storage_Xmlel} -> + get_data(LUser, LServer, Data, Mod, [Storage_Xmlel | Storage_Xmlels]); + error -> + get_data(LUser, LServer, Data, Mod, [Xmlel | Storage_Xmlels]) end. get_data(LUser, LServer) -> - get_all_data(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). - -get_all_data(LUser, LServer, mnesia) -> - lists:flatten( - mnesia:dirty_select(private_storage, - [{#private_storage{usns = {LUser, LServer, '_'}, - xml = '$1'}, - [], ['$1']}])); -get_all_data(LUser, LServer, odbc) -> - case catch odbc_queries:get_private_data(LServer, LUser) of - {selected, Res} -> - lists:flatmap( - fun({_, SData}) -> - case fxml_stream:parse_element(SData) of - #xmlel{} = El -> - [El]; - _ -> - [] - end - end, Res); - _ -> - [] - end; -get_all_data(LUser, LServer, riak) -> - case ejabberd_riak:get_by_index( - private_storage, private_storage_schema(), - <<"us">>, {LUser, LServer}) of - {ok, Res} -> - [El || #private_storage{xml = El} <- Res]; - _ -> - [] - end. - -private_storage_schema() -> - {record_info(fields, private_storage), #private_storage{}}. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:get_all_data(LUser, LServer). remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - remove_user(LUser, LServer, - gen_mod:db_type(Server, ?MODULE)). + Mod = gen_mod:db_mod(Server, ?MODULE), + Mod:remove_user(LUser, LServer). -remove_user(LUser, LServer, mnesia) -> - F = fun () -> - Namespaces = mnesia:select(private_storage, - [{#private_storage{usns = - {LUser, - LServer, - '$1'}, - _ = '_'}, - [], ['$$']}]), - lists:foreach(fun ([Namespace]) -> - mnesia:delete({private_storage, - {LUser, LServer, - Namespace}}) - end, - Namespaces) - end, - mnesia:transaction(F); -remove_user(LUser, LServer, odbc) -> - odbc_queries:del_user_private_storage(LServer, LUser); -remove_user(LUser, LServer, riak) -> - {atomic, ejabberd_riak:delete_by_index(private_storage, - <<"us">>, {LUser, LServer})}. - -update_table() -> - Fields = record_info(fields, private_storage), - case mnesia:table_info(private_storage, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - private_storage, Fields, set, - fun(#private_storage{usns = {U, _, _}}) -> U end, - fun(#private_storage{usns = {U, S, NS}, xml = El} = R) -> - R#private_storage{usns = {iolist_to_binary(U), - iolist_to_binary(S), - iolist_to_binary(NS)}, - xml = fxml:to_xmlel(El)} - end); - _ -> - ?INFO_MSG("Recreating private_storage table", []), - mnesia:transform_table(private_storage, ignore, Fields) - end. - -export(_Server) -> - [{private_storage, - fun(Host, #private_storage{usns = {LUser, LServer, XMLNS}, - xml = Data}) - when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - LXMLNS = ejabberd_odbc:escape(XMLNS), - SData = - ejabberd_odbc:escape(fxml:element_to_binary(Data)), - odbc_queries:set_private_data_sql(Username, LXMLNS, - SData); - (_Host, _R) -> - [] - end}]. +export(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:export(LServer). import(LServer) -> - [{<<"select username, namespace, data from private_storage;">>, - fun([LUser, XMLNS, XML]) -> - El = #xmlel{} = fxml_stream:parse_element(XML), - #private_storage{usns = {LUser, LServer, XMLNS}, - xml = El} - end}]. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:import(LServer). -import(_LServer, mnesia, #private_storage{} = PS) -> - mnesia:dirty_write(PS); - -import(_LServer, riak, #private_storage{usns = {LUser, LServer, _}} = PS) -> - ejabberd_riak:put(PS, private_storage_schema(), - [{'2i', [{<<"us">>, {LUser, LServer}}]}]); -import(_, _, _) -> - pass. +import(LServer, DBType, PD) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, PD). mod_opt_type(db_type) -> fun gen_mod:v_db/1; mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; diff --git a/src/mod_private_mnesia.erl b/src/mod_private_mnesia.erl new file mode 100644 index 000000000..7a852c4f8 --- /dev/null +++ b/src/mod_private_mnesia.erl @@ -0,0 +1,97 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_private_mnesia). +-behaviour(mod_private). + +%% API +-export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2, + import/2]). + +-include("jlib.hrl"). +-include("mod_private.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + mnesia:create_table(private_storage, + [{disc_only_copies, [node()]}, + {attributes, + record_info(fields, private_storage)}]), + update_table(). + +set_data(LUser, LServer, Data) -> + F = fun () -> + lists:foreach( + fun({XmlNS, Xmlel}) -> + mnesia:write( + #private_storage{ + usns = {LUser, LServer, XmlNS}, + xml = Xmlel}) + end, Data) + end, + mnesia:transaction(F). + +get_data(LUser, LServer, XmlNS) -> + case mnesia:dirty_read(private_storage, {LUser, LServer, XmlNS}) of + [#private_storage{xml = Storage_Xmlel}] -> + {ok, Storage_Xmlel}; + _ -> + error + end. + +get_all_data(LUser, LServer) -> + lists:flatten( + mnesia:dirty_select(private_storage, + [{#private_storage{usns = {LUser, LServer, '_'}, + xml = '$1'}, + [], ['$1']}])). + +remove_user(LUser, LServer) -> + F = fun () -> + Namespaces = mnesia:select(private_storage, + [{#private_storage{usns = + {LUser, + LServer, + '$1'}, + _ = '_'}, + [], ['$$']}]), + lists:foreach(fun ([Namespace]) -> + mnesia:delete({private_storage, + {LUser, LServer, + Namespace}}) + end, + Namespaces) + end, + mnesia:transaction(F). + +import(_LServer, #private_storage{} = PS) -> + mnesia:dirty_write(PS). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +update_table() -> + Fields = record_info(fields, private_storage), + case mnesia:table_info(private_storage, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + private_storage, Fields, set, + fun(#private_storage{usns = {U, _, _}}) -> U end, + fun(#private_storage{usns = {U, S, NS}, xml = El} = R) -> + R#private_storage{usns = {iolist_to_binary(U), + iolist_to_binary(S), + iolist_to_binary(NS)}, + xml = fxml:to_xmlel(El)} + end); + _ -> + ?INFO_MSG("Recreating private_storage table", []), + mnesia:transform_table(private_storage, ignore, Fields) + end. diff --git a/src/mod_private_riak.erl b/src/mod_private_riak.erl new file mode 100644 index 000000000..11cfa4770 --- /dev/null +++ b/src/mod_private_riak.erl @@ -0,0 +1,67 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_private_riak). + +-behaviour(mod_private). + +%% API +-export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2, + import/2]). + +-include("jlib.hrl"). +-include("mod_private.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +set_data(LUser, LServer, Data) -> + lists:foreach( + fun({XMLNS, El}) -> + ejabberd_riak:put(#private_storage{usns = {LUser, LServer, XMLNS}, + xml = El}, + private_storage_schema(), + [{'2i', [{<<"us">>, {LUser, LServer}}]}]) + end, Data), + {atomic, ok}. + +get_data(LUser, LServer, XMLNS) -> + case ejabberd_riak:get(private_storage, private_storage_schema(), + {LUser, LServer, XMLNS}) of + {ok, #private_storage{xml = El}} -> + {ok, El}; + _ -> + error + end. + +get_all_data(LUser, LServer) -> + case ejabberd_riak:get_by_index( + private_storage, private_storage_schema(), + <<"us">>, {LUser, LServer}) of + {ok, Res} -> + [El || #private_storage{xml = El} <- Res]; + _ -> + [] + end. + +remove_user(LUser, LServer) -> + {atomic, ejabberd_riak:delete_by_index(private_storage, + <<"us">>, {LUser, LServer})}. + +import(_LServer, #private_storage{usns = {LUser, LServer, _}} = PS) -> + ejabberd_riak:put(PS, private_storage_schema(), + [{'2i', [{<<"us">>, {LUser, LServer}}]}]). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +private_storage_schema() -> + {record_info(fields, private_storage), #private_storage{}}. diff --git a/src/mod_private_sql.erl b/src/mod_private_sql.erl new file mode 100644 index 000000000..1b77c48b6 --- /dev/null +++ b/src/mod_private_sql.erl @@ -0,0 +1,97 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_private_sql). + +-behaviour(mod_private). + +%% API +-export([init/2, set_data/3, get_data/3, get_all_data/2, remove_user/2, + import/1, import/2, export/1]). + +-include("jlib.hrl"). +-include("mod_private.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +set_data(LUser, LServer, Data) -> + F = fun() -> + lists:foreach( + fun({XMLNS, El}) -> + SData = fxml:element_to_binary(El), + odbc_queries:set_private_data( + LServer, LUser, XMLNS, SData) + end, Data) + end, + ejabberd_odbc:sql_transaction(LServer, F). + +get_data(LUser, LServer, XMLNS) -> + case catch odbc_queries:get_private_data(LServer, LUser, XMLNS) of + {selected, [{SData}]} -> + case fxml_stream:parse_element(SData) of + Data when is_record(Data, xmlel) -> + {ok, Data}; + _ -> + error + end; + _ -> + error + end. + +get_all_data(LUser, LServer) -> + case catch odbc_queries:get_private_data(LServer, LUser) of + {selected, Res} -> + lists:flatmap( + fun({_, SData}) -> + case fxml_stream:parse_element(SData) of + #xmlel{} = El -> + [El]; + _ -> + [] + end + end, Res); + _ -> + [] + end. + +remove_user(LUser, LServer) -> + odbc_queries:del_user_private_storage(LServer, LUser). + +export(_Server) -> + [{private_storage, + fun(Host, #private_storage{usns = {LUser, LServer, XMLNS}, + xml = Data}) + when LServer == Host -> + Username = ejabberd_odbc:escape(LUser), + LXMLNS = ejabberd_odbc:escape(XMLNS), + SData = + ejabberd_odbc:escape(fxml:element_to_binary(Data)), + odbc_queries:set_private_data_sql(Username, LXMLNS, + SData); + (_Host, _R) -> + [] + end}]. + +import(LServer) -> + [{<<"select username, namespace, data from private_storage;">>, + fun([LUser, XMLNS, XML]) -> + El = #xmlel{} = fxml_stream:parse_element(XML), + #private_storage{usns = {LUser, LServer, XMLNS}, + xml = El} + end}]. + +import(_, _) -> + pass. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== From ae69f09257c81847f1d2cbb1ea82d398df8b81bb Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 13 Apr 2016 17:37:52 +0300 Subject: [PATCH 07/15] Clean mod_vcard.erl from DB specific code --- include/mod_vcard.hrl | 8 + src/mod_vcard.erl | 698 ++++----------------------------------- src/mod_vcard_mnesia.erl | 213 ++++++++++++ src/mod_vcard_riak.erl | 151 +++++++++ src/mod_vcard_sql.erl | 268 +++++++++++++++ 5 files changed, 711 insertions(+), 627 deletions(-) create mode 100644 include/mod_vcard.hrl create mode 100644 src/mod_vcard_mnesia.erl create mode 100644 src/mod_vcard_riak.erl create mode 100644 src/mod_vcard_sql.erl diff --git a/include/mod_vcard.hrl b/include/mod_vcard.hrl new file mode 100644 index 000000000..3bd62b2eb --- /dev/null +++ b/include/mod_vcard.hrl @@ -0,0 +1,8 @@ +-record(vcard_search, + {us, user, luser, fn, lfn, family, lfamily, given, + lgiven, middle, lmiddle, nickname, lnickname, bday, + lbday, ctry, lctry, locality, llocality, email, lemail, + orgname, lorgname, orgunit, lorgunit}). + +-record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()} | binary(), + vcard = #xmlel{} :: xmlel()}). diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index 4d7c80d57..e5f5d9e3c 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -25,8 +25,6 @@ -module(mod_vcard). --compile([{parse_transform, ejabberd_sql_pt}]). - -author('alexey@process-one.net'). -protocol({xep, 54, '1.2'}). @@ -35,54 +33,31 @@ -behaviour(gen_mod). -export([start/2, init/3, stop/1, get_sm_features/5, - process_local_iq/3, process_sm_iq/3, reindex_vcards/0, + process_local_iq/3, process_sm_iq/3, string2lower/1, remove_user/2, export/1, import/1, import/3, - mod_opt_type/1, set_vcard/3]). + mod_opt_type/1, set_vcard/3, make_vcard_search/4]). -include("ejabberd.hrl"). -include("logger.hrl"). --include("ejabberd_sql_pt.hrl"). - -include("jlib.hrl"). +-include("mod_vcard.hrl"). -define(JUD_MATCHES, 30). --record(vcard_search, - {us, user, luser, fn, lfn, family, lfamily, given, - lgiven, middle, lmiddle, nickname, lnickname, bday, - lbday, ctry, lctry, locality, llocality, email, lemail, - orgname, lorgname, orgunit, lorgunit}). - --record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()} | binary(), - vcard = #xmlel{} :: xmlel()}). - -define(PROCNAME, ejabberd_mod_vcard). +-callback init(binary(), gen_mod:opts()) -> any(). +-callback import(binary(), #vcard{} | #vcard_search{}) -> ok | pass. +-callback get_vcard(binary(), binary()) -> [xmlel()] | error. +-callback set_vcard(binary(), binary(), + xmlel(), #vcard_search{}) -> {atomic, any()}. +-callback search(binary(), [{binary(), [binary()]}], boolean(), + infinity | pos_integer()) -> [binary()]. +-callback remove_user(binary(), binary()) -> {atomic, any()}. + start(Host, Opts) -> - case gen_mod:db_type(Host, Opts) of - mnesia -> - mnesia:create_table(vcard, - [{disc_only_copies, [node()]}, - {attributes, record_info(fields, vcard)}]), - mnesia:create_table(vcard_search, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, vcard_search)}]), - update_tables(), - mnesia:add_table_index(vcard_search, luser), - mnesia:add_table_index(vcard_search, lfn), - mnesia:add_table_index(vcard_search, lfamily), - mnesia:add_table_index(vcard_search, lgiven), - mnesia:add_table_index(vcard_search, lmiddle), - mnesia:add_table_index(vcard_search, lnickname), - mnesia:add_table_index(vcard_search, lbday), - mnesia:add_table_index(vcard_search, lctry), - mnesia:add_table_index(vcard_search, llocality), - mnesia:add_table_index(vcard_search, lemail), - mnesia:add_table_index(vcard_search, lorgname), - mnesia:add_table_index(vcard_search, lorgunit); - _ -> ok - end, + Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod:init(Host, Opts), ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50), IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, @@ -206,38 +181,10 @@ process_sm_iq(From, To, end. get_vcard(LUser, LServer) -> - get_vcard(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:get_vcard(LUser, LServer). -get_vcard(LUser, LServer, mnesia) -> - US = {LUser, LServer}, - F = fun () -> mnesia:read({vcard, US}) end, - case mnesia:transaction(F) of - {atomic, Rs} -> - lists:map(fun (R) -> R#vcard.vcard end, Rs); - {aborted, _Reason} -> error - end; -get_vcard(LUser, LServer, odbc) -> - case catch odbc_queries:get_vcard(LServer, LUser) of - {selected, [{SVCARD}]} -> - case fxml_stream:parse_element(SVCARD) of - {error, _Reason} -> error; - VCARD -> [VCARD] - end; - {selected, []} -> []; - _ -> error - end; -get_vcard(LUser, LServer, riak) -> - case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of - {ok, R} -> - [R#vcard.vcard]; - {error, notfound} -> - []; - _ -> - error - end. - -set_vcard(User, LServer, VCARD) -> +make_vcard_search(User, LUser, LServer, VCARD) -> FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]), Family = fxml:get_path_s(VCARD, [{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]), @@ -266,7 +213,6 @@ set_vcard(User, LServer, VCARD) -> <<"">> -> EMail2; _ -> EMail1 end, - LUser = jid:nodeprep(User), LFN = string2lower(FN), LFamily = string2lower(Family), LGiven = string2lower(Given), @@ -278,80 +224,42 @@ set_vcard(User, LServer, VCARD) -> LEMail = string2lower(EMail), LOrgName = string2lower(OrgName), LOrgUnit = string2lower(OrgUnit), - if (LUser == error) -> - {error, badarg}; - true -> - case gen_mod:db_type(LServer, ?MODULE) of - mnesia -> - US = {LUser, LServer}, - F = fun () -> - mnesia:write(#vcard{us = US, vcard = VCARD}), - mnesia:write(#vcard_search{us = US, - user = {User, LServer}, - luser = LUser, fn = FN, - lfn = LFN, - family = Family, - lfamily = LFamily, - given = Given, - lgiven = LGiven, - middle = Middle, - lmiddle = LMiddle, - nickname = Nickname, - lnickname = LNickname, - bday = BDay, - lbday = LBDay, - ctry = CTRY, - lctry = LCTRY, - locality = Locality, - llocality = LLocality, - email = EMail, - lemail = LEMail, - orgname = OrgName, - lorgname = LOrgName, - orgunit = OrgUnit, - lorgunit = LOrgUnit}) - end, - mnesia:transaction(F); - riak -> - US = {LUser, LServer}, - ejabberd_riak:put(#vcard{us = US, vcard = VCARD}, - vcard_schema(), - [{'2i', [{<<"user">>, User}, - {<<"luser">>, LUser}, - {<<"fn">>, FN}, - {<<"lfn">>, LFN}, - {<<"family">>, Family}, - {<<"lfamily">>, LFamily}, - {<<"given">>, Given}, - {<<"lgiven">>, LGiven}, - {<<"middle">>, Middle}, - {<<"lmiddle">>, LMiddle}, - {<<"nickname">>, Nickname}, - {<<"lnickname">>, LNickname}, - {<<"bday">>, BDay}, - {<<"lbday">>, LBDay}, - {<<"ctry">>, CTRY}, - {<<"lctry">>, LCTRY}, - {<<"locality">>, Locality}, - {<<"llocality">>, LLocality}, - {<<"email">>, EMail}, - {<<"lemail">>, LEMail}, - {<<"orgname">>, OrgName}, - {<<"lorgname">>, LOrgName}, - {<<"orgunit">>, OrgUnit}, - {<<"lorgunit">>, LOrgUnit}]}]); - odbc -> - SVCARD = fxml:element_to_binary(VCARD), - odbc_queries:set_vcard(LServer, LUser, BDay, CTRY, - EMail, FN, Family, Given, LBDay, - LCTRY, LEMail, LFN, LFamily, - LGiven, LLocality, LMiddle, - LNickname, LOrgName, LOrgUnit, - Locality, Middle, Nickname, OrgName, - OrgUnit, SVCARD, User) - end, - ejabberd_hooks:run(vcard_set, LServer, - [LUser, LServer, VCARD]) + US = {LUser, LServer}, + #vcard_search{us = US, + user = {User, LServer}, + luser = LUser, fn = FN, + lfn = LFN, + family = Family, + lfamily = LFamily, + given = Given, + lgiven = LGiven, + middle = Middle, + lmiddle = LMiddle, + nickname = Nickname, + lnickname = LNickname, + bday = BDay, + lbday = LBDay, + ctry = CTRY, + lctry = LCTRY, + locality = Locality, + llocality = LLocality, + email = EMail, + lemail = LEMail, + orgname = OrgName, + lorgname = LOrgName, + orgunit = OrgUnit, + lorgunit = LOrgUnit}. + +set_vcard(User, LServer, VCARD) -> + case jid:nodeprep(User) of + error -> + {error, badarg}; + LUser -> + VCardSearch = make_vcard_search(User, LUser, LServer, VCARD), + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:set_vcard(LUser, LServer, VCARD, VCardSearch), + ejabberd_hooks:run(vcard_set, LServer, + [LUser, LServer, VCARD]) end. string2lower(String) -> @@ -655,500 +563,36 @@ record_to_item(_LServer, #vcard_search{} = R) -> ?FIELD(<<"orgunit">>, (R#vcard_search.orgunit))]}. search(LServer, Data) -> - DBType = gen_mod:db_type(LServer, ?MODULE), - MatchSpec = make_matchspec(LServer, Data, DBType), + Mod = gen_mod:db_mod(LServer, ?MODULE), AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE, allow_return_all, fun(B) when is_boolean(B) -> B end, false), - search(LServer, MatchSpec, AllowReturnAll, DBType). - -search(LServer, MatchSpec, AllowReturnAll, mnesia) -> - if (MatchSpec == #vcard_search{_ = '_'}) and - not AllowReturnAll -> - []; - true -> - case catch mnesia:dirty_select(vcard_search, - [{MatchSpec, [], ['$_']}]) - of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), []; - Rs -> - case gen_mod:get_module_opt(LServer, ?MODULE, matches, - fun(infinity) -> infinity; - (I) when is_integer(I), - I>0 -> - I - end, ?JUD_MATCHES) of - infinity -> - Rs; - Val -> - lists:sublist(Rs, Val) - end - end - end; -search(LServer, MatchSpec, AllowReturnAll, odbc) -> - if (MatchSpec == <<"">>) and not AllowReturnAll -> []; - true -> - Limit = case gen_mod:get_module_opt(LServer, ?MODULE, matches, - fun(infinity) -> infinity; - (I) when is_integer(I), - I>0 -> - I - end, ?JUD_MATCHES) of - infinity -> - <<"">>; - Val -> - [<<" LIMIT ">>, - jlib:integer_to_binary(Val)] - end, - case catch ejabberd_odbc:sql_query(LServer, - [<<"select username, fn, family, given, " - "middle, nickname, bday, ctry, " - "locality, email, orgname, orgunit " - "from vcard_search ">>, - MatchSpec, Limit, <<";">>]) - of - {selected, - [<<"username">>, <<"fn">>, <<"family">>, <<"given">>, - <<"middle">>, <<"nickname">>, <<"bday">>, <<"ctry">>, - <<"locality">>, <<"email">>, <<"orgname">>, - <<"orgunit">>], - Rs} - when is_list(Rs) -> - Rs; - Error -> ?ERROR_MSG("~p", [Error]), [] - end - end; -search(_LServer, _MatchSpec, _AllowReturnAll, riak) -> - []. - -make_matchspec(LServer, Data, mnesia) -> - GlobMatch = #vcard_search{_ = '_'}, - Match = filter_fields(Data, GlobMatch, LServer, mnesia), - Match; -make_matchspec(LServer, Data, odbc) -> - filter_fields(Data, <<"">>, LServer, odbc); -make_matchspec(_LServer, _Data, riak) -> - []. - -filter_fields([], Match, _LServer, mnesia) -> Match; -filter_fields([], Match, _LServer, odbc) -> - case Match of - <<"">> -> <<"">>; - _ -> [<<" where ">>, Match] - end; -filter_fields([{SVar, [Val]} | Ds], Match, LServer, - mnesia) - when is_binary(Val) and (Val /= <<"">>) -> - LVal = string2lower(Val), - NewMatch = case SVar of - <<"user">> -> - case gen_mod:get_module_opt(LServer, ?MODULE, - search_all_hosts, - fun(B) when is_boolean(B) -> - B - end, true) - of - true -> Match#vcard_search{luser = make_val(LVal)}; - false -> - Host = find_my_host(LServer), - Match#vcard_search{us = {make_val(LVal), Host}} - end; - <<"fn">> -> Match#vcard_search{lfn = make_val(LVal)}; - <<"last">> -> - Match#vcard_search{lfamily = make_val(LVal)}; - <<"first">> -> - Match#vcard_search{lgiven = make_val(LVal)}; - <<"middle">> -> - Match#vcard_search{lmiddle = make_val(LVal)}; - <<"nick">> -> - Match#vcard_search{lnickname = make_val(LVal)}; - <<"bday">> -> - Match#vcard_search{lbday = make_val(LVal)}; - <<"ctry">> -> - Match#vcard_search{lctry = make_val(LVal)}; - <<"locality">> -> - Match#vcard_search{llocality = make_val(LVal)}; - <<"email">> -> - Match#vcard_search{lemail = make_val(LVal)}; - <<"orgname">> -> - Match#vcard_search{lorgname = make_val(LVal)}; - <<"orgunit">> -> - Match#vcard_search{lorgunit = make_val(LVal)}; - _ -> Match - end, - filter_fields(Ds, NewMatch, LServer, mnesia); -filter_fields([{SVar, [Val]} | Ds], Match, LServer, - odbc) - when is_binary(Val) and (Val /= <<"">>) -> - LVal = string2lower(Val), - NewMatch = case SVar of - <<"user">> -> make_val(Match, <<"lusername">>, LVal); - <<"fn">> -> make_val(Match, <<"lfn">>, LVal); - <<"last">> -> make_val(Match, <<"lfamily">>, LVal); - <<"first">> -> make_val(Match, <<"lgiven">>, LVal); - <<"middle">> -> make_val(Match, <<"lmiddle">>, LVal); - <<"nick">> -> make_val(Match, <<"lnickname">>, LVal); - <<"bday">> -> make_val(Match, <<"lbday">>, LVal); - <<"ctry">> -> make_val(Match, <<"lctry">>, LVal); - <<"locality">> -> - make_val(Match, <<"llocality">>, LVal); - <<"email">> -> make_val(Match, <<"lemail">>, LVal); - <<"orgname">> -> make_val(Match, <<"lorgname">>, LVal); - <<"orgunit">> -> make_val(Match, <<"lorgunit">>, LVal); - _ -> Match - end, - filter_fields(Ds, NewMatch, LServer, odbc); -filter_fields([_ | Ds], Match, LServer, DBType) -> - filter_fields(Ds, Match, LServer, DBType). - -make_val(Match, Field, Val) -> - Condition = case str:suffix(<<"*">>, Val) of - true -> - Val1 = str:substr(Val, 1, byte_size(Val) - 1), - SVal = <<(ejabberd_odbc:escape_like(Val1))/binary, - "%">>, - [Field, <<" LIKE '">>, SVal, <<"'">>]; - _ -> - SVal = ejabberd_odbc:escape(Val), - [Field, <<" = '">>, SVal, <<"'">>] - end, - case Match of - <<"">> -> Condition; - _ -> [Match, <<" and ">>, Condition] - end. - -make_val(Val) -> - case str:suffix(<<"*">>, Val) of - true -> [str:substr(Val, 1, byte_size(Val) - 1)] ++ '_'; - _ -> Val - end. - -find_my_host(LServer) -> - Parts = str:tokens(LServer, <<".">>), - find_my_host(Parts, ?MYHOSTS). - -find_my_host([], _Hosts) -> ?MYNAME; -find_my_host([_ | Tail] = Parts, Hosts) -> - Domain = parts_to_string(Parts), - case lists:member(Domain, Hosts) of - true -> Domain; - false -> find_my_host(Tail, Hosts) - end. - -parts_to_string(Parts) -> - str:strip(list_to_binary( - lists:map(fun (S) -> <> end, Parts)), - right, $.). + MaxMatch = gen_mod:get_module_opt(LServer, ?MODULE, matches, + fun(infinity) -> infinity; + (I) when is_integer(I), + I>0 -> + I + end, ?JUD_MATCHES), + Mod:search(LServer, Data, AllowReturnAll, MaxMatch). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -set_vcard_t(R, _) -> - US = R#vcard.us, - User = US, - VCARD = R#vcard.vcard, - FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]), - Family = fxml:get_path_s(VCARD, - [{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]), - Given = fxml:get_path_s(VCARD, - [{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]), - Middle = fxml:get_path_s(VCARD, - [{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]), - Nickname = fxml:get_path_s(VCARD, - [{elem, <<"NICKNAME">>}, cdata]), - BDay = fxml:get_path_s(VCARD, - [{elem, <<"BDAY">>}, cdata]), - CTRY = fxml:get_path_s(VCARD, - [{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]), - Locality = fxml:get_path_s(VCARD, - [{elem, <<"ADR">>}, {elem, <<"LOCALITY">>}, - cdata]), - EMail = fxml:get_path_s(VCARD, - [{elem, <<"EMAIL">>}, cdata]), - OrgName = fxml:get_path_s(VCARD, - [{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]), - OrgUnit = fxml:get_path_s(VCARD, - [{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]), - {LUser, _LServer} = US, - LFN = string2lower(FN), - LFamily = string2lower(Family), - LGiven = string2lower(Given), - LMiddle = string2lower(Middle), - LNickname = string2lower(Nickname), - LBDay = string2lower(BDay), - LCTRY = string2lower(CTRY), - LLocality = string2lower(Locality), - LEMail = string2lower(EMail), - LOrgName = string2lower(OrgName), - LOrgUnit = string2lower(OrgUnit), - mnesia:write(#vcard_search{us = US, user = User, - luser = LUser, fn = FN, lfn = LFN, - family = Family, lfamily = LFamily, - given = Given, lgiven = LGiven, - middle = Middle, lmiddle = LMiddle, - nickname = Nickname, - lnickname = LNickname, bday = BDay, - lbday = LBDay, ctry = CTRY, lctry = LCTRY, - locality = Locality, - llocality = LLocality, email = EMail, - lemail = LEMail, orgname = OrgName, - lorgname = LOrgName, orgunit = OrgUnit, - lorgunit = LOrgUnit}). - -reindex_vcards() -> - F = fun () -> mnesia:foldl(fun set_vcard_t/2, [], vcard) - end, - mnesia:transaction(F). - remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - remove_user(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:remove_user(LUser, LServer). -remove_user(LUser, LServer, mnesia) -> - US = {LUser, LServer}, - F = fun () -> - mnesia:delete({vcard, US}), - mnesia:delete({vcard_search, US}) - end, - mnesia:transaction(F); -remove_user(LUser, LServer, odbc) -> - ejabberd_odbc:sql_transaction( - LServer, - fun() -> - ejabberd_odbc:sql_query_t( - ?SQL("delete from vcard where username=%(LUser)s")), - ejabberd_odbc:sql_query_t( - ?SQL("delete from vcard_search where lusername=%(LUser)s")) - end); -remove_user(LUser, LServer, riak) -> - {atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}. - -update_tables() -> - update_vcard_table(), - update_vcard_search_table(). - -update_vcard_table() -> - Fields = record_info(fields, vcard), - case mnesia:table_info(vcard, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - vcard, Fields, set, - fun(#vcard{us = {U, _}}) -> U end, - fun(#vcard{us = {U, S}, vcard = El} = R) -> - R#vcard{us = {iolist_to_binary(U), - iolist_to_binary(S)}, - vcard = fxml:to_xmlel(El)} - end); - _ -> - ?INFO_MSG("Recreating vcard table", []), - mnesia:transform_table(vcard, ignore, Fields) - end. - -update_vcard_search_table() -> - Fields = record_info(fields, vcard_search), - case mnesia:table_info(vcard_search, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - vcard_search, Fields, set, - fun(#vcard_search{us = {U, _}}) -> U end, - fun(#vcard_search{} = VS) -> - [vcard_search | L] = tuple_to_list(VS), - NewL = lists:map( - fun({U, S}) -> - {iolist_to_binary(U), - iolist_to_binary(S)}; - (Str) -> - iolist_to_binary(Str) - end, L), - list_to_tuple([vcard_search | NewL]) - end); - _ -> - ?INFO_MSG("Recreating vcard_search table", []), - mnesia:transform_table(vcard_search, ignore, Fields) - end. - -vcard_schema() -> - {record_info(fields, vcard), #vcard{}}. - -export(_Server) -> - [{vcard, - fun(Host, #vcard{us = {LUser, LServer}, vcard = VCARD}) - when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - SVCARD = - ejabberd_odbc:escape(fxml:element_to_binary(VCARD)), - [[<<"delete from vcard where username='">>, Username, <<"';">>], - [<<"insert into vcard(username, vcard) values ('">>, - Username, <<"', '">>, SVCARD, <<"');">>]]; - (_Host, _R) -> - [] - end}, - {vcard_search, - fun(Host, #vcard_search{user = {User, LServer}, luser = LUser, - fn = FN, lfn = LFN, family = Family, - lfamily = LFamily, given = Given, - lgiven = LGiven, middle = Middle, - lmiddle = LMiddle, nickname = Nickname, - lnickname = LNickname, bday = BDay, - lbday = LBDay, ctry = CTRY, lctry = LCTRY, - locality = Locality, llocality = LLocality, - email = EMail, lemail = LEMail, - orgname = OrgName, lorgname = LOrgName, - orgunit = OrgUnit, lorgunit = LOrgUnit}) - when LServer == Host -> - Username = ejabberd_odbc:escape(User), - LUsername = ejabberd_odbc:escape(LUser), - SFN = ejabberd_odbc:escape(FN), - SLFN = ejabberd_odbc:escape(LFN), - SFamily = ejabberd_odbc:escape(Family), - SLFamily = ejabberd_odbc:escape(LFamily), - SGiven = ejabberd_odbc:escape(Given), - SLGiven = ejabberd_odbc:escape(LGiven), - SMiddle = ejabberd_odbc:escape(Middle), - SLMiddle = ejabberd_odbc:escape(LMiddle), - SNickname = ejabberd_odbc:escape(Nickname), - SLNickname = ejabberd_odbc:escape(LNickname), - SBDay = ejabberd_odbc:escape(BDay), - SLBDay = ejabberd_odbc:escape(LBDay), - SCTRY = ejabberd_odbc:escape(CTRY), - SLCTRY = ejabberd_odbc:escape(LCTRY), - SLocality = ejabberd_odbc:escape(Locality), - SLLocality = ejabberd_odbc:escape(LLocality), - SEMail = ejabberd_odbc:escape(EMail), - SLEMail = ejabberd_odbc:escape(LEMail), - SOrgName = ejabberd_odbc:escape(OrgName), - SLOrgName = ejabberd_odbc:escape(LOrgName), - SOrgUnit = ejabberd_odbc:escape(OrgUnit), - SLOrgUnit = ejabberd_odbc:escape(LOrgUnit), - [[<<"delete from vcard_search where lusername='">>, - LUsername, <<"';">>], - [<<"insert into vcard_search( username, " - "lusername, fn, lfn, family, lfamily, " - " given, lgiven, middle, lmiddle, " - "nickname, lnickname, bday, lbday, " - "ctry, lctry, locality, llocality, " - " email, lemail, orgname, lorgname, " - "orgunit, lorgunit)values (">>, - <<" '">>, Username, <<"', '">>, LUsername, - <<"', '">>, SFN, <<"', '">>, SLFN, - <<"', '">>, SFamily, <<"', '">>, SLFamily, - <<"', '">>, SGiven, <<"', '">>, SLGiven, - <<"', '">>, SMiddle, <<"', '">>, SLMiddle, - <<"', '">>, SNickname, <<"', '">>, SLNickname, - <<"', '">>, SBDay, <<"', '">>, SLBDay, - <<"', '">>, SCTRY, <<"', '">>, SLCTRY, - <<"', '">>, SLocality, <<"', '">>, SLLocality, - <<"', '">>, SEMail, <<"', '">>, SLEMail, - <<"', '">>, SOrgName, <<"', '">>, SLOrgName, - <<"', '">>, SOrgUnit, <<"', '">>, SLOrgUnit, - <<"');">>]]; - (_Host, _R) -> - [] - end}]. +export(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:export(LServer). import(LServer) -> - [{<<"select username, vcard from vcard;">>, - fun([LUser, SVCard]) -> - #xmlel{} = VCARD = fxml_stream:parse_element(SVCard), - #vcard{us = {LUser, LServer}, vcard = VCARD} - end}, - {<<"select username, lusername, fn, lfn, family, lfamily, " - "given, lgiven, middle, lmiddle, nickname, lnickname, " - "bday, lbday, ctry, lctry, locality, llocality, email, " - "lemail, orgname, lorgname, orgunit, lorgunit from vcard_search;">>, - fun([User, LUser, FN, LFN, - Family, LFamily, Given, LGiven, - Middle, LMiddle, Nickname, LNickname, - BDay, LBDay, CTRY, LCTRY, Locality, LLocality, - EMail, LEMail, OrgName, LOrgName, OrgUnit, LOrgUnit]) -> - #vcard_search{us = {LUser, LServer}, - user = {User, LServer}, luser = LUser, - fn = FN, lfn = LFN, family = Family, - lfamily = LFamily, given = Given, - lgiven = LGiven, middle = Middle, - lmiddle = LMiddle, nickname = Nickname, - lnickname = LNickname, bday = BDay, - lbday = LBDay, ctry = CTRY, lctry = LCTRY, - locality = Locality, llocality = LLocality, - email = EMail, lemail = LEMail, - orgname = OrgName, lorgname = LOrgName, - orgunit = OrgUnit, lorgunit = LOrgUnit} - end}]. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:import(LServer). -import(_LServer, mnesia, #vcard{} = VCard) -> - mnesia:dirty_write(VCard); -import(_LServer, mnesia, #vcard_search{} = S) -> - mnesia:dirty_write(S); -import(_LServer, riak, #vcard{us = {LUser, _}, vcard = El} = VCard) -> - FN = fxml:get_path_s(El, [{elem, <<"FN">>}, cdata]), - Family = fxml:get_path_s(El, - [{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]), - Given = fxml:get_path_s(El, - [{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]), - Middle = fxml:get_path_s(El, - [{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]), - Nickname = fxml:get_path_s(El, - [{elem, <<"NICKNAME">>}, cdata]), - BDay = fxml:get_path_s(El, - [{elem, <<"BDAY">>}, cdata]), - CTRY = fxml:get_path_s(El, - [{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]), - Locality = fxml:get_path_s(El, - [{elem, <<"ADR">>}, {elem, <<"LOCALITY">>}, - cdata]), - EMail1 = fxml:get_path_s(El, - [{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]), - EMail2 = fxml:get_path_s(El, - [{elem, <<"EMAIL">>}, cdata]), - OrgName = fxml:get_path_s(El, - [{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]), - OrgUnit = fxml:get_path_s(El, - [{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]), - EMail = case EMail1 of - <<"">> -> EMail2; - _ -> EMail1 - end, - LFN = string2lower(FN), - LFamily = string2lower(Family), - LGiven = string2lower(Given), - LMiddle = string2lower(Middle), - LNickname = string2lower(Nickname), - LBDay = string2lower(BDay), - LCTRY = string2lower(CTRY), - LLocality = string2lower(Locality), - LEMail = string2lower(EMail), - LOrgName = string2lower(OrgName), - LOrgUnit = string2lower(OrgUnit), - ejabberd_riak:put(VCard, vcard_schema(), - [{'2i', [{<<"user">>, LUser}, - {<<"luser">>, LUser}, - {<<"fn">>, FN}, - {<<"lfn">>, LFN}, - {<<"family">>, Family}, - {<<"lfamily">>, LFamily}, - {<<"given">>, Given}, - {<<"lgiven">>, LGiven}, - {<<"middle">>, Middle}, - {<<"lmiddle">>, LMiddle}, - {<<"nickname">>, Nickname}, - {<<"lnickname">>, LNickname}, - {<<"bday">>, BDay}, - {<<"lbday">>, LBDay}, - {<<"ctry">>, CTRY}, - {<<"lctry">>, LCTRY}, - {<<"locality">>, Locality}, - {<<"llocality">>, LLocality}, - {<<"email">>, EMail}, - {<<"lemail">>, LEMail}, - {<<"orgname">>, OrgName}, - {<<"lorgname">>, LOrgName}, - {<<"orgunit">>, OrgUnit}, - {<<"lorgunit">>, LOrgUnit}]}]); -import(_LServer, riak, #vcard_search{}) -> - ok; -import(_, _, _) -> - pass. +import(LServer, DBType, VCard) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, VCard). mod_opt_type(allow_return_all) -> fun (B) when is_boolean(B) -> B end; diff --git a/src/mod_vcard_mnesia.erl b/src/mod_vcard_mnesia.erl new file mode 100644 index 000000000..781a135c8 --- /dev/null +++ b/src/mod_vcard_mnesia.erl @@ -0,0 +1,213 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_vcard_mnesia). + +-behaviour(mod_vcard). + +%% API +-export([init/2, import/2, get_vcard/2, set_vcard/4, search/4, remove_user/2]). + +-include("ejabberd.hrl"). +-include("jlib.hrl"). +-include("mod_vcard.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + mnesia:create_table(vcard, + [{disc_only_copies, [node()]}, + {attributes, record_info(fields, vcard)}]), + mnesia:create_table(vcard_search, + [{disc_copies, [node()]}, + {attributes, + record_info(fields, vcard_search)}]), + update_tables(), + mnesia:add_table_index(vcard_search, luser), + mnesia:add_table_index(vcard_search, lfn), + mnesia:add_table_index(vcard_search, lfamily), + mnesia:add_table_index(vcard_search, lgiven), + mnesia:add_table_index(vcard_search, lmiddle), + mnesia:add_table_index(vcard_search, lnickname), + mnesia:add_table_index(vcard_search, lbday), + mnesia:add_table_index(vcard_search, lctry), + mnesia:add_table_index(vcard_search, llocality), + mnesia:add_table_index(vcard_search, lemail), + mnesia:add_table_index(vcard_search, lorgname), + mnesia:add_table_index(vcard_search, lorgunit). + +get_vcard(LUser, LServer) -> + US = {LUser, LServer}, + F = fun () -> mnesia:read({vcard, US}) end, + case mnesia:transaction(F) of + {atomic, Rs} -> + lists:map(fun (R) -> R#vcard.vcard end, Rs); + {aborted, _Reason} -> error + end. + +set_vcard(LUser, LServer, VCARD, VCardSearch) -> + US = {LUser, LServer}, + F = fun () -> + mnesia:write(#vcard{us = US, vcard = VCARD}), + mnesia:write(VCardSearch) + end, + mnesia:transaction(F). + +search(LServer, Data, AllowReturnAll, MaxMatch) -> + MatchSpec = make_matchspec(LServer, Data), + if (MatchSpec == #vcard_search{_ = '_'}) and + not AllowReturnAll -> + []; + true -> + case catch mnesia:dirty_select(vcard_search, + [{MatchSpec, [], ['$_']}]) of + {'EXIT', Reason} -> + ?ERROR_MSG("~p", [Reason]), []; + Rs -> + case MaxMatch of + infinity -> + Rs; + Val -> + lists:sublist(Rs, Val) + end + end + end. + +remove_user(LUser, LServer) -> + US = {LUser, LServer}, + F = fun () -> + mnesia:delete({vcard, US}), + mnesia:delete({vcard_search, US}) + end, + mnesia:transaction(F). + +import(_LServer, #vcard{} = VCard) -> + mnesia:dirty_write(VCard); +import(_LServer, #vcard_search{} = S) -> + mnesia:dirty_write(S). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +update_tables() -> + update_vcard_table(), + update_vcard_search_table(). + +update_vcard_table() -> + Fields = record_info(fields, vcard), + case mnesia:table_info(vcard, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + vcard, Fields, set, + fun(#vcard{us = {U, _}}) -> U end, + fun(#vcard{us = {U, S}, vcard = El} = R) -> + R#vcard{us = {iolist_to_binary(U), + iolist_to_binary(S)}, + vcard = fxml:to_xmlel(El)} + end); + _ -> + ?INFO_MSG("Recreating vcard table", []), + mnesia:transform_table(vcard, ignore, Fields) + end. + +update_vcard_search_table() -> + Fields = record_info(fields, vcard_search), + case mnesia:table_info(vcard_search, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + vcard_search, Fields, set, + fun(#vcard_search{us = {U, _}}) -> U end, + fun(#vcard_search{} = VS) -> + [vcard_search | L] = tuple_to_list(VS), + NewL = lists:map( + fun({U, S}) -> + {iolist_to_binary(U), + iolist_to_binary(S)}; + (Str) -> + iolist_to_binary(Str) + end, L), + list_to_tuple([vcard_search | NewL]) + end); + _ -> + ?INFO_MSG("Recreating vcard_search table", []), + mnesia:transform_table(vcard_search, ignore, Fields) + end. + +make_matchspec(LServer, Data) -> + GlobMatch = #vcard_search{_ = '_'}, + Match = filter_fields(Data, GlobMatch, LServer), + Match. + +filter_fields([], Match, _LServer) -> + Match; +filter_fields([{SVar, [Val]} | Ds], Match, LServer) + when is_binary(Val) and (Val /= <<"">>) -> + LVal = mod_vcard:string2lower(Val), + NewMatch = case SVar of + <<"user">> -> + case gen_mod:get_module_opt(LServer, ?MODULE, + search_all_hosts, + fun(B) when is_boolean(B) -> + B + end, true) of + true -> Match#vcard_search{luser = make_val(LVal)}; + false -> + Host = find_my_host(LServer), + Match#vcard_search{us = {make_val(LVal), Host}} + end; + <<"fn">> -> Match#vcard_search{lfn = make_val(LVal)}; + <<"last">> -> + Match#vcard_search{lfamily = make_val(LVal)}; + <<"first">> -> + Match#vcard_search{lgiven = make_val(LVal)}; + <<"middle">> -> + Match#vcard_search{lmiddle = make_val(LVal)}; + <<"nick">> -> + Match#vcard_search{lnickname = make_val(LVal)}; + <<"bday">> -> + Match#vcard_search{lbday = make_val(LVal)}; + <<"ctry">> -> + Match#vcard_search{lctry = make_val(LVal)}; + <<"locality">> -> + Match#vcard_search{llocality = make_val(LVal)}; + <<"email">> -> + Match#vcard_search{lemail = make_val(LVal)}; + <<"orgname">> -> + Match#vcard_search{lorgname = make_val(LVal)}; + <<"orgunit">> -> + Match#vcard_search{lorgunit = make_val(LVal)}; + _ -> Match + end, + filter_fields(Ds, NewMatch, LServer); +filter_fields([_ | Ds], Match, LServer) -> + filter_fields(Ds, Match, LServer). + +make_val(Val) -> + case str:suffix(<<"*">>, Val) of + true -> [str:substr(Val, 1, byte_size(Val) - 1)] ++ '_'; + _ -> Val + end. + +find_my_host(LServer) -> + Parts = str:tokens(LServer, <<".">>), + find_my_host(Parts, ?MYHOSTS). + +find_my_host([], _Hosts) -> ?MYNAME; +find_my_host([_ | Tail] = Parts, Hosts) -> + Domain = parts_to_string(Parts), + case lists:member(Domain, Hosts) of + true -> Domain; + false -> find_my_host(Tail, Hosts) + end. + +parts_to_string(Parts) -> + str:strip(list_to_binary( + lists:map(fun (S) -> <> end, Parts)), + right, $.). diff --git a/src/mod_vcard_riak.erl b/src/mod_vcard_riak.erl new file mode 100644 index 000000000..386347387 --- /dev/null +++ b/src/mod_vcard_riak.erl @@ -0,0 +1,151 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_vcard_riak). + +-behaviour(mod_vcard). + +%% API +-export([init/2, get_vcard/2, set_vcard/4, search/4, remove_user/2, + import/2]). + +-include("jlib.hrl"). +-include("mod_vcard.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +get_vcard(LUser, LServer) -> + case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of + {ok, R} -> + [R#vcard.vcard]; + {error, notfound} -> + []; + _ -> + error + end. + +set_vcard(LUser, LServer, VCARD, + #vcard_search{user = {User, _}, + fn = FN, + lfn = LFN, + family = Family, + lfamily = LFamily, + given = Given, + lgiven = LGiven, + middle = Middle, + lmiddle = LMiddle, + nickname = Nickname, + lnickname = LNickname, + bday = BDay, + lbday = LBDay, + ctry = CTRY, + lctry = LCTRY, + locality = Locality, + llocality = LLocality, + email = EMail, + lemail = LEMail, + orgname = OrgName, + lorgname = LOrgName, + orgunit = OrgUnit, + lorgunit = LOrgUnit}) -> + US = {LUser, LServer}, + {atomic, + ejabberd_riak:put(#vcard{us = US, vcard = VCARD}, + vcard_schema(), + [{'2i', [{<<"user">>, User}, + {<<"luser">>, LUser}, + {<<"fn">>, FN}, + {<<"lfn">>, LFN}, + {<<"family">>, Family}, + {<<"lfamily">>, LFamily}, + {<<"given">>, Given}, + {<<"lgiven">>, LGiven}, + {<<"middle">>, Middle}, + {<<"lmiddle">>, LMiddle}, + {<<"nickname">>, Nickname}, + {<<"lnickname">>, LNickname}, + {<<"bday">>, BDay}, + {<<"lbday">>, LBDay}, + {<<"ctry">>, CTRY}, + {<<"lctry">>, LCTRY}, + {<<"locality">>, Locality}, + {<<"llocality">>, LLocality}, + {<<"email">>, EMail}, + {<<"lemail">>, LEMail}, + {<<"orgname">>, OrgName}, + {<<"lorgname">>, LOrgName}, + {<<"orgunit">>, OrgUnit}, + {<<"lorgunit">>, LOrgUnit}]}])}. + +search(_LServer, _Data, _AllowReturnAll, _MaxMatch) -> + []. + +remove_user(LUser, LServer) -> + {atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}. + +import(_LServer, #vcard{us = {LUser, LServer}, vcard = El} = VCard) -> + #vcard_search{fn = FN, + lfn = LFN, + family = Family, + lfamily = LFamily, + given = Given, + lgiven = LGiven, + middle = Middle, + lmiddle = LMiddle, + nickname = Nickname, + lnickname = LNickname, + bday = BDay, + lbday = LBDay, + ctry = CTRY, + lctry = LCTRY, + locality = Locality, + llocality = LLocality, + email = EMail, + lemail = LEMail, + orgname = OrgName, + lorgname = LOrgName, + orgunit = OrgUnit, + lorgunit = LOrgUnit} = + mod_vcard:make_vcard_search(LUser, LUser, LServer, El), + ejabberd_riak:put(VCard, vcard_schema(), + [{'2i', [{<<"user">>, LUser}, + {<<"luser">>, LUser}, + {<<"fn">>, FN}, + {<<"lfn">>, LFN}, + {<<"family">>, Family}, + {<<"lfamily">>, LFamily}, + {<<"given">>, Given}, + {<<"lgiven">>, LGiven}, + {<<"middle">>, Middle}, + {<<"lmiddle">>, LMiddle}, + {<<"nickname">>, Nickname}, + {<<"lnickname">>, LNickname}, + {<<"bday">>, BDay}, + {<<"lbday">>, LBDay}, + {<<"ctry">>, CTRY}, + {<<"lctry">>, LCTRY}, + {<<"locality">>, Locality}, + {<<"llocality">>, LLocality}, + {<<"email">>, EMail}, + {<<"lemail">>, LEMail}, + {<<"orgname">>, OrgName}, + {<<"lorgname">>, LOrgName}, + {<<"orgunit">>, OrgUnit}, + {<<"lorgunit">>, LOrgUnit}]}]); +import(_LServer, #vcard_search{}) -> + ok. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +vcard_schema() -> + {record_info(fields, vcard), #vcard{}}. diff --git a/src/mod_vcard_sql.erl b/src/mod_vcard_sql.erl new file mode 100644 index 000000000..fff39091c --- /dev/null +++ b/src/mod_vcard_sql.erl @@ -0,0 +1,268 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_vcard_sql). + +-compile([{parse_transform, ejabberd_sql_pt}]). + +-behaviour(mod_vcard). + +%% API +-export([init/2, get_vcard/2, set_vcard/4, search/4, remove_user/2, + import/1, import/2, export/1]). + +-include("jlib.hrl"). +-include("mod_vcard.hrl"). +-include("logger.hrl"). +-include("ejabberd_sql_pt.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +get_vcard(LUser, LServer) -> + case catch odbc_queries:get_vcard(LServer, LUser) of + {selected, [{SVCARD}]} -> + case fxml_stream:parse_element(SVCARD) of + {error, _Reason} -> error; + VCARD -> [VCARD] + end; + {selected, []} -> []; + _ -> error + end. + +set_vcard(LUser, LServer, VCARD, + #vcard_search{user = {User, _}, + fn = FN, + lfn = LFN, + family = Family, + lfamily = LFamily, + given = Given, + lgiven = LGiven, + middle = Middle, + lmiddle = LMiddle, + nickname = Nickname, + lnickname = LNickname, + bday = BDay, + lbday = LBDay, + ctry = CTRY, + lctry = LCTRY, + locality = Locality, + llocality = LLocality, + email = EMail, + lemail = LEMail, + orgname = OrgName, + lorgname = LOrgName, + orgunit = OrgUnit, + lorgunit = LOrgUnit}) -> + SVCARD = fxml:element_to_binary(VCARD), + odbc_queries:set_vcard(LServer, LUser, BDay, CTRY, + EMail, FN, Family, Given, LBDay, + LCTRY, LEMail, LFN, LFamily, + LGiven, LLocality, LMiddle, + LNickname, LOrgName, LOrgUnit, + Locality, Middle, Nickname, OrgName, + OrgUnit, SVCARD, User). + +search(LServer, Data, AllowReturnAll, MaxMatch) -> + MatchSpec = make_matchspec(LServer, Data), + if (MatchSpec == <<"">>) and not AllowReturnAll -> []; + true -> + Limit = case MaxMatch of + infinity -> + <<"">>; + Val -> + [<<" LIMIT ">>, jlib:integer_to_binary(Val)] + end, + case catch ejabberd_odbc:sql_query( + LServer, + [<<"select username, fn, family, given, " + "middle, nickname, bday, ctry, " + "locality, email, orgname, orgunit " + "from vcard_search ">>, + MatchSpec, Limit, <<";">>]) of + {selected, + [<<"username">>, <<"fn">>, <<"family">>, <<"given">>, + <<"middle">>, <<"nickname">>, <<"bday">>, <<"ctry">>, + <<"locality">>, <<"email">>, <<"orgname">>, + <<"orgunit">>], Rs} when is_list(Rs) -> + Rs; + Error -> + ?ERROR_MSG("~p", [Error]), [] + end + end. + +remove_user(LUser, LServer) -> + ejabberd_odbc:sql_transaction( + LServer, + fun() -> + ejabberd_odbc:sql_query_t( + ?SQL("delete from vcard where username=%(LUser)s")), + ejabberd_odbc:sql_query_t( + ?SQL("delete from vcard_search where lusername=%(LUser)s")) + end). + +export(_Server) -> + [{vcard, + fun(Host, #vcard{us = {LUser, LServer}, vcard = VCARD}) + when LServer == Host -> + Username = ejabberd_odbc:escape(LUser), + SVCARD = + ejabberd_odbc:escape(fxml:element_to_binary(VCARD)), + [[<<"delete from vcard where username='">>, Username, <<"';">>], + [<<"insert into vcard(username, vcard) values ('">>, + Username, <<"', '">>, SVCARD, <<"');">>]]; + (_Host, _R) -> + [] + end}, + {vcard_search, + fun(Host, #vcard_search{user = {User, LServer}, luser = LUser, + fn = FN, lfn = LFN, family = Family, + lfamily = LFamily, given = Given, + lgiven = LGiven, middle = Middle, + lmiddle = LMiddle, nickname = Nickname, + lnickname = LNickname, bday = BDay, + lbday = LBDay, ctry = CTRY, lctry = LCTRY, + locality = Locality, llocality = LLocality, + email = EMail, lemail = LEMail, + orgname = OrgName, lorgname = LOrgName, + orgunit = OrgUnit, lorgunit = LOrgUnit}) + when LServer == Host -> + Username = ejabberd_odbc:escape(User), + LUsername = ejabberd_odbc:escape(LUser), + SFN = ejabberd_odbc:escape(FN), + SLFN = ejabberd_odbc:escape(LFN), + SFamily = ejabberd_odbc:escape(Family), + SLFamily = ejabberd_odbc:escape(LFamily), + SGiven = ejabberd_odbc:escape(Given), + SLGiven = ejabberd_odbc:escape(LGiven), + SMiddle = ejabberd_odbc:escape(Middle), + SLMiddle = ejabberd_odbc:escape(LMiddle), + SNickname = ejabberd_odbc:escape(Nickname), + SLNickname = ejabberd_odbc:escape(LNickname), + SBDay = ejabberd_odbc:escape(BDay), + SLBDay = ejabberd_odbc:escape(LBDay), + SCTRY = ejabberd_odbc:escape(CTRY), + SLCTRY = ejabberd_odbc:escape(LCTRY), + SLocality = ejabberd_odbc:escape(Locality), + SLLocality = ejabberd_odbc:escape(LLocality), + SEMail = ejabberd_odbc:escape(EMail), + SLEMail = ejabberd_odbc:escape(LEMail), + SOrgName = ejabberd_odbc:escape(OrgName), + SLOrgName = ejabberd_odbc:escape(LOrgName), + SOrgUnit = ejabberd_odbc:escape(OrgUnit), + SLOrgUnit = ejabberd_odbc:escape(LOrgUnit), + [[<<"delete from vcard_search where lusername='">>, + LUsername, <<"';">>], + [<<"insert into vcard_search( username, " + "lusername, fn, lfn, family, lfamily, " + " given, lgiven, middle, lmiddle, " + "nickname, lnickname, bday, lbday, " + "ctry, lctry, locality, llocality, " + " email, lemail, orgname, lorgname, " + "orgunit, lorgunit)values (">>, + <<" '">>, Username, <<"', '">>, LUsername, + <<"', '">>, SFN, <<"', '">>, SLFN, + <<"', '">>, SFamily, <<"', '">>, SLFamily, + <<"', '">>, SGiven, <<"', '">>, SLGiven, + <<"', '">>, SMiddle, <<"', '">>, SLMiddle, + <<"', '">>, SNickname, <<"', '">>, SLNickname, + <<"', '">>, SBDay, <<"', '">>, SLBDay, + <<"', '">>, SCTRY, <<"', '">>, SLCTRY, + <<"', '">>, SLocality, <<"', '">>, SLLocality, + <<"', '">>, SEMail, <<"', '">>, SLEMail, + <<"', '">>, SOrgName, <<"', '">>, SLOrgName, + <<"', '">>, SOrgUnit, <<"', '">>, SLOrgUnit, + <<"');">>]]; + (_Host, _R) -> + [] + end}]. + +import(LServer) -> + [{<<"select username, vcard from vcard;">>, + fun([LUser, SVCard]) -> + #xmlel{} = VCARD = fxml_stream:parse_element(SVCard), + #vcard{us = {LUser, LServer}, vcard = VCARD} + end}, + {<<"select username, lusername, fn, lfn, family, lfamily, " + "given, lgiven, middle, lmiddle, nickname, lnickname, " + "bday, lbday, ctry, lctry, locality, llocality, email, " + "lemail, orgname, lorgname, orgunit, lorgunit from vcard_search;">>, + fun([User, LUser, FN, LFN, + Family, LFamily, Given, LGiven, + Middle, LMiddle, Nickname, LNickname, + BDay, LBDay, CTRY, LCTRY, Locality, LLocality, + EMail, LEMail, OrgName, LOrgName, OrgUnit, LOrgUnit]) -> + #vcard_search{us = {LUser, LServer}, + user = {User, LServer}, luser = LUser, + fn = FN, lfn = LFN, family = Family, + lfamily = LFamily, given = Given, + lgiven = LGiven, middle = Middle, + lmiddle = LMiddle, nickname = Nickname, + lnickname = LNickname, bday = BDay, + lbday = LBDay, ctry = CTRY, lctry = LCTRY, + locality = Locality, llocality = LLocality, + email = EMail, lemail = LEMail, + orgname = OrgName, lorgname = LOrgName, + orgunit = OrgUnit, lorgunit = LOrgUnit} + end}]. + +import(_, _) -> + pass. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +make_matchspec(LServer, Data) -> + filter_fields(Data, <<"">>, LServer). + +filter_fields([], Match, _LServer) -> + case Match of + <<"">> -> <<"">>; + _ -> [<<" where ">>, Match] + end; +filter_fields([{SVar, [Val]} | Ds], Match, LServer) + when is_binary(Val) and (Val /= <<"">>) -> + LVal = mod_vcard:string2lower(Val), + NewMatch = case SVar of + <<"user">> -> make_val(Match, <<"lusername">>, LVal); + <<"fn">> -> make_val(Match, <<"lfn">>, LVal); + <<"last">> -> make_val(Match, <<"lfamily">>, LVal); + <<"first">> -> make_val(Match, <<"lgiven">>, LVal); + <<"middle">> -> make_val(Match, <<"lmiddle">>, LVal); + <<"nick">> -> make_val(Match, <<"lnickname">>, LVal); + <<"bday">> -> make_val(Match, <<"lbday">>, LVal); + <<"ctry">> -> make_val(Match, <<"lctry">>, LVal); + <<"locality">> -> + make_val(Match, <<"llocality">>, LVal); + <<"email">> -> make_val(Match, <<"lemail">>, LVal); + <<"orgname">> -> make_val(Match, <<"lorgname">>, LVal); + <<"orgunit">> -> make_val(Match, <<"lorgunit">>, LVal); + _ -> Match + end, + filter_fields(Ds, NewMatch, LServer); +filter_fields([_ | Ds], Match, LServer) -> + filter_fields(Ds, Match, LServer). + +make_val(Match, Field, Val) -> + Condition = case str:suffix(<<"*">>, Val) of + true -> + Val1 = str:substr(Val, 1, byte_size(Val) - 1), + SVal = <<(ejabberd_odbc:escape_like(Val1))/binary, + "%">>, + [Field, <<" LIKE '">>, SVal, <<"'">>]; + _ -> + SVal = ejabberd_odbc:escape(Val), + [Field, <<" = '">>, SVal, <<"'">>] + end, + case Match of + <<"">> -> Condition; + _ -> [Match, <<" and ">>, Condition] + end. From 0b439a7d5b28a5de8d4c2e108333ad20189b580e Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Wed, 13 Apr 2016 21:07:32 +0300 Subject: [PATCH 08/15] Clean mod_muc.erl from DB specific code --- src/mod_muc.erl | 447 ++++------------------------------------- src/mod_muc_mnesia.erl | 163 +++++++++++++++ src/mod_muc_riak.erl | 117 +++++++++++ src/mod_muc_sql.erl | 202 +++++++++++++++++++ 4 files changed, 519 insertions(+), 410 deletions(-) create mode 100644 src/mod_muc_mnesia.erl create mode 100644 src/mod_muc_riak.erl create mode 100644 src/mod_muc_sql.erl diff --git a/src/mod_muc.erl b/src/mod_muc.erl index 9d4b4985c..95631e25c 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -48,6 +48,7 @@ export/1, import/1, import/3, + opts_to_binary/1, can_use_nick/4]). -export([init/1, handle_call/3, handle_cast/2, @@ -72,6 +73,17 @@ -define(MAX_ROOMS_DISCOITEMS, 100). +-type muc_room_opts() :: [{atom(), any()}]. +-callback init(binary(), gen_mod:opts()) -> any(). +-callback import(binary(), #muc_room{} | #muc_registered{}) -> ok | pass. +-callback store_room(binary(), binary(), binary(), list()) -> {atomic, any()}. +-callback restore_room(binary(), binary(), binary()) -> muc_room_opts() | error. +-callback forget_room(binary(), binary(), binary()) -> {atomic, any()}. +-callback can_use_nick(binary(), binary(), jid(), binary()) -> boolean(). +-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}. + %%==================================================================== %% API %%==================================================================== @@ -125,83 +137,19 @@ create_room(Host, Name, From, Nick, Opts) -> store_room(ServerHost, Host, Name, Opts) -> LServer = jid:nameprep(ServerHost), - store_room(LServer, Host, Name, Opts, - gen_mod:db_type(LServer, ?MODULE)). - -store_room(_LServer, Host, Name, Opts, mnesia) -> - F = fun () -> - mnesia:write(#muc_room{name_host = {Name, Host}, - opts = Opts}) - end, - mnesia:transaction(F); -store_room(_LServer, Host, Name, Opts, riak) -> - {atomic, ejabberd_riak:put(#muc_room{name_host = {Name, Host}, - opts = Opts}, - muc_room_schema())}; -store_room(LServer, Host, Name, Opts, odbc) -> - SName = ejabberd_odbc:escape(Name), - SHost = ejabberd_odbc:escape(Host), - SOpts = ejabberd_odbc:encode_term(Opts), - F = fun () -> - odbc_queries:update_t(<<"muc_room">>, - [<<"name">>, <<"host">>, <<"opts">>], - [SName, SHost, SOpts], - [<<"name='">>, SName, <<"' and host='">>, - SHost, <<"'">>]) - end, - ejabberd_odbc:sql_transaction(LServer, F). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:store_room(LServer, Host, Name, Opts). restore_room(ServerHost, Host, Name) -> LServer = jid:nameprep(ServerHost), - restore_room(LServer, Host, Name, - gen_mod:db_type(LServer, ?MODULE)). - -restore_room(_LServer, Host, Name, mnesia) -> - case catch mnesia:dirty_read(muc_room, {Name, Host}) of - [#muc_room{opts = Opts}] -> Opts; - _ -> error - end; -restore_room(_LServer, Host, Name, riak) -> - case ejabberd_riak:get(muc_room, muc_room_schema(), {Name, Host}) of - {ok, #muc_room{opts = Opts}} -> Opts; - _ -> error - end; -restore_room(LServer, Host, Name, odbc) -> - SName = ejabberd_odbc:escape(Name), - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query(LServer, - [<<"select opts from muc_room where name='">>, - SName, <<"' and host='">>, SHost, - <<"';">>]) - of - {selected, [<<"opts">>], [[Opts]]} -> - opts_to_binary(ejabberd_odbc:decode_term(Opts)); - _ -> error - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:restore_room(LServer, Host, Name). forget_room(ServerHost, Host, Name) -> LServer = jid:nameprep(ServerHost), - forget_room(LServer, Host, Name, - gen_mod:db_type(LServer, ?MODULE)). - -forget_room(LServer, Host, Name, mnesia) -> remove_room_mam(LServer, Host, Name), - F = fun () -> mnesia:delete({muc_room, {Name, Host}}) - end, - mnesia:transaction(F); -forget_room(LServer, Host, Name, riak) -> - remove_room_mam(LServer, Host, Name), - {atomic, ejabberd_riak:delete(muc_room, {Name, Host})}; -forget_room(LServer, Host, Name, odbc) -> - remove_room_mam(LServer, Host, Name), - SName = ejabberd_odbc:escape(Name), - SHost = ejabberd_odbc:escape(Host), - F = fun () -> - ejabberd_odbc:sql_query_t([<<"delete from muc_room where name='">>, - SName, <<"' and host='">>, SHost, - <<"';">>]) - end, - ejabberd_odbc:sql_transaction(LServer, F). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:forget_room(LServer, Host, Name). remove_room_mam(LServer, Host, Name) -> case gen_mod:is_loaded(LServer, mod_mam) of @@ -233,48 +181,8 @@ process_iq_disco_items(Host, From, To, can_use_nick(_ServerHost, _Host, _JID, <<"">>) -> false; can_use_nick(ServerHost, Host, JID, Nick) -> LServer = jid:nameprep(ServerHost), - can_use_nick(LServer, Host, JID, Nick, - gen_mod:db_type(LServer, ?MODULE)). - -can_use_nick(_LServer, Host, JID, Nick, mnesia) -> - {LUser, LServer, _} = jid:tolower(JID), - LUS = {LUser, LServer}, - case catch mnesia:dirty_select(muc_registered, - [{#muc_registered{us_host = '$1', - nick = Nick, _ = '_'}, - [{'==', {element, 2, '$1'}, Host}], - ['$_']}]) - of - {'EXIT', _Reason} -> true; - [] -> true; - [#muc_registered{us_host = {U, _Host}}] -> U == LUS - end; -can_use_nick(LServer, Host, JID, Nick, riak) -> - {LUser, LServer, _} = jid:tolower(JID), - LUS = {LUser, LServer}, - case ejabberd_riak:get_by_index(muc_registered, - muc_registered_schema(), - <<"nick_host">>, {Nick, Host}) of - {ok, []} -> - true; - {ok, [#muc_registered{us_host = {U, _Host}}]} -> - U == LUS; - {error, _} -> - true - end; -can_use_nick(LServer, Host, JID, Nick, odbc) -> - SJID = - jid:to_string(jid:tolower(jid:remove_resource(JID))), - SNick = ejabberd_odbc:escape(Nick), - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query(LServer, - [<<"select jid from muc_registered ">>, - <<"where nick='">>, SNick, - <<"' and host='">>, SHost, <<"';">>]) - of - {selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1; - _ -> true - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:can_use_nick(LServer, Host, JID, Nick). %%==================================================================== %% gen_server callbacks @@ -283,21 +191,8 @@ can_use_nick(LServer, Host, JID, Nick, odbc) -> init([Host, Opts]) -> MyHost = gen_mod:get_opt_host(Host, Opts, <<"conference.@HOST@">>), - case gen_mod:db_type(Host, Opts) of - mnesia -> - mnesia:create_table(muc_room, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, muc_room)}]), - mnesia:create_table(muc_registered, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, muc_registered)}]), - update_tables(MyHost), - mnesia:add_table_index(muc_registered, nick); - _ -> - ok - end, + Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod:init(Host, [{host, MyHost}|Opts]), mnesia:create_table(muc_online_room, [{ram_copies, [node()]}, {attributes, record_info(fields, muc_online_room)}]), @@ -647,43 +542,8 @@ check_create_roomid(ServerHost, RoomID) -> get_rooms(ServerHost, Host) -> LServer = jid:nameprep(ServerHost), - get_rooms(LServer, Host, - gen_mod:db_type(LServer, ?MODULE)). - -get_rooms(_LServer, Host, mnesia) -> - case catch mnesia:dirty_select(muc_room, - [{#muc_room{name_host = {'_', Host}, - _ = '_'}, - [], ['$_']}]) - of - {'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), []; - Rs -> Rs - end; -get_rooms(_LServer, Host, riak) -> - case ejabberd_riak:get(muc_room, muc_room_schema()) of - {ok, Rs} -> - lists:filter( - fun(#muc_room{name_host = {_, H}}) -> - Host == H - end, Rs); - _Err -> - [] - end; -get_rooms(LServer, Host, odbc) -> - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query(LServer, - [<<"select name, opts from muc_room ">>, - <<"where host='">>, SHost, <<"';">>]) - of - {selected, [<<"name">>, <<"opts">>], RoomOpts} -> - lists:map(fun ([Room, Opts]) -> - #muc_room{name_host = {Room, Host}, - opts = opts_to_binary( - ejabberd_odbc:decode_term(Opts))} - end, - RoomOpts); - Err -> ?ERROR_MSG("failed to get rooms: ~p", [Err]), [] - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:get_rooms(LServer, Host). load_permanent_rooms(Host, ServerHost, Access, HistorySize, RoomShaper) -> @@ -873,41 +733,8 @@ iq_get_unique(From) -> get_nick(ServerHost, Host, From) -> LServer = jid:nameprep(ServerHost), - get_nick(LServer, Host, From, - gen_mod:db_type(LServer, ?MODULE)). - -get_nick(_LServer, Host, From, mnesia) -> - {LUser, LServer, _} = jid:tolower(From), - LUS = {LUser, LServer}, - case catch mnesia:dirty_read(muc_registered, - {LUS, Host}) - of - {'EXIT', _Reason} -> error; - [] -> error; - [#muc_registered{nick = Nick}] -> Nick - end; -get_nick(LServer, Host, From, riak) -> - {LUser, LServer, _} = jid:tolower(From), - US = {LUser, LServer}, - case ejabberd_riak:get(muc_registered, - muc_registered_schema(), - {US, Host}) of - {ok, #muc_registered{nick = Nick}} -> Nick; - {error, _} -> error - end; -get_nick(LServer, Host, From, odbc) -> - SJID = - ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))), - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query(LServer, - [<<"select nick from muc_registered where " - "jid='">>, - SJID, <<"' and host='">>, SHost, - <<"';">>]) - of - {selected, [<<"nick">>], [[Nick]]} -> Nick; - _ -> error - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:get_nick(LServer, Host, From). iq_get_register_info(ServerHost, Host, From, Lang) -> {Nick, Registered} = case get_nick(ServerHost, Host, @@ -946,107 +773,8 @@ iq_get_register_info(ServerHost, Host, From, Lang) -> set_nick(ServerHost, Host, From, Nick) -> LServer = jid:nameprep(ServerHost), - set_nick(LServer, Host, From, Nick, - gen_mod:db_type(LServer, ?MODULE)). - -set_nick(_LServer, Host, From, Nick, mnesia) -> - {LUser, LServer, _} = jid:tolower(From), - LUS = {LUser, LServer}, - F = fun () -> - case Nick of - <<"">> -> - mnesia:delete({muc_registered, {LUS, Host}}), ok; - _ -> - Allow = case mnesia:select(muc_registered, - [{#muc_registered{us_host = - '$1', - nick = Nick, - _ = '_'}, - [{'==', {element, 2, '$1'}, - Host}], - ['$_']}]) - of - [] -> true; - [#muc_registered{us_host = {U, _Host}}] -> - U == LUS - end, - if Allow -> - mnesia:write(#muc_registered{us_host = {LUS, Host}, - nick = Nick}), - ok; - true -> false - end - end - end, - mnesia:transaction(F); -set_nick(LServer, Host, From, Nick, riak) -> - {LUser, LServer, _} = jid:tolower(From), - LUS = {LUser, LServer}, - {atomic, - case Nick of - <<"">> -> - ejabberd_riak:delete(muc_registered, {LUS, Host}); - _ -> - Allow = case ejabberd_riak:get_by_index( - muc_registered, - muc_registered_schema(), - <<"nick_host">>, {Nick, Host}) of - {ok, []} -> - true; - {ok, [#muc_registered{us_host = {U, _Host}}]} -> - U == LUS; - {error, _} -> - false - end, - if Allow -> - ejabberd_riak:put(#muc_registered{us_host = {LUS, Host}, - nick = Nick}, - muc_registered_schema(), - [{'2i', [{<<"nick_host">>, - {Nick, Host}}]}]); - true -> - false - end - end}; -set_nick(LServer, Host, From, Nick, odbc) -> - JID = - jid:to_string(jid:tolower(jid:remove_resource(From))), - SJID = ejabberd_odbc:escape(JID), - SNick = ejabberd_odbc:escape(Nick), - SHost = ejabberd_odbc:escape(Host), - F = fun () -> - case Nick of - <<"">> -> - ejabberd_odbc:sql_query_t([<<"delete from muc_registered where ">>, - <<"jid='">>, SJID, - <<"' and host='">>, Host, - <<"';">>]), - ok; - _ -> - Allow = case - ejabberd_odbc:sql_query_t([<<"select jid from muc_registered ">>, - <<"where nick='">>, - SNick, - <<"' and host='">>, - SHost, <<"';">>]) - of - {selected, [<<"jid">>], [[J]]} -> J == JID; - _ -> true - end, - if Allow -> - odbc_queries:update_t(<<"muc_registered">>, - [<<"jid">>, <<"host">>, - <<"nick">>], - [SJID, SHost, SNick], - [<<"jid='">>, SJID, - <<"' and host='">>, SHost, - <<"'">>]), - ok; - true -> false - end - end - end, - ejabberd_odbc:sql_transaction(LServer, F). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:set_nick(LServer, Host, From, Nick). iq_set_register_info(ServerHost, Host, From, Nick, Lang) -> @@ -1192,118 +920,17 @@ opts_to_binary(Opts) -> Opt end, Opts). -update_tables(Host) -> - update_muc_room_table(Host), - update_muc_registered_table(Host). +export(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:export(LServer). -muc_room_schema() -> - {record_info(fields, muc_room), #muc_room{}}. +import(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:import(LServer). -muc_registered_schema() -> - {record_info(fields, muc_registered), #muc_registered{}}. - -update_muc_room_table(_Host) -> - Fields = record_info(fields, muc_room), - case mnesia:table_info(muc_room, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - muc_room, Fields, set, - fun(#muc_room{name_host = {N, _}}) -> N end, - fun(#muc_room{name_host = {N, H}, - opts = Opts} = R) -> - R#muc_room{name_host = {iolist_to_binary(N), - iolist_to_binary(H)}, - opts = opts_to_binary(Opts)} - end); - _ -> - ?INFO_MSG("Recreating muc_room table", []), - mnesia:transform_table(muc_room, ignore, Fields) - end. - -update_muc_registered_table(_Host) -> - Fields = record_info(fields, muc_registered), - case mnesia:table_info(muc_registered, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - muc_registered, Fields, set, - fun(#muc_registered{us_host = {_, H}}) -> H end, - fun(#muc_registered{us_host = {{U, S}, H}, - nick = Nick} = R) -> - R#muc_registered{us_host = {{iolist_to_binary(U), - iolist_to_binary(S)}, - iolist_to_binary(H)}, - nick = iolist_to_binary(Nick)} - end); - _ -> - ?INFO_MSG("Recreating muc_registered table", []), - mnesia:transform_table(muc_registered, ignore, Fields) - end. - -export(_Server) -> - [{muc_room, - fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) -> - case str:suffix(Host, RoomHost) of - true -> - SName = ejabberd_odbc:escape(Name), - SRoomHost = ejabberd_odbc:escape(RoomHost), - SOpts = ejabberd_odbc:encode_term(Opts), - [[<<"delete from muc_room where name='">>, SName, - <<"' and host='">>, SRoomHost, <<"';">>], - [<<"insert into muc_room(name, host, opts) ", - "values (">>, - <<"'">>, SName, <<"', '">>, SRoomHost, - <<"', '">>, SOpts, <<"');">>]]; - false -> - [] - end - end}, - {muc_registered, - fun(Host, #muc_registered{us_host = {{U, S}, RoomHost}, - nick = Nick}) -> - case str:suffix(Host, RoomHost) of - true -> - SJID = ejabberd_odbc:escape( - jid:to_string( - jid:make(U, S, <<"">>))), - SNick = ejabberd_odbc:escape(Nick), - SRoomHost = ejabberd_odbc:escape(RoomHost), - [[<<"delete from muc_registered where jid='">>, - SJID, <<"' and host='">>, SRoomHost, <<"';">>], - [<<"insert into muc_registered(jid, host, " - "nick) values ('">>, - SJID, <<"', '">>, SRoomHost, <<"', '">>, SNick, - <<"');">>]]; - false -> - [] - end - end}]. - -import(_LServer) -> - [{<<"select name, host, opts from muc_room;">>, - fun([Name, RoomHost, SOpts]) -> - Opts = opts_to_binary(ejabberd_odbc:decode_term(SOpts)), - #muc_room{name_host = {Name, RoomHost}, opts = Opts} - end}, - {<<"select jid, host, nick from muc_registered;">>, - fun([J, RoomHost, Nick]) -> - #jid{user = U, server = S} = - jid:from_string(J), - #muc_registered{us_host = {{U, S}, RoomHost}, - nick = Nick} - end}]. - -import(_LServer, mnesia, #muc_room{} = R) -> - mnesia:dirty_write(R); -import(_LServer, mnesia, #muc_registered{} = R) -> - mnesia:dirty_write(R); -import(_LServer, riak, #muc_room{} = R) -> - ejabberd_riak:put(R, muc_room_schema()); -import(_LServer, riak, - #muc_registered{us_host = {_, Host}, nick = Nick} = R) -> - ejabberd_riak:put(R, muc_registered_schema(), - [{'2i', [{<<"nick_host">>, {Nick, Host}}]}]); -import(_, _, _) -> - pass. +import(LServer, DBType, Data) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, Data). mod_opt_type(access) -> fun (A) when is_atom(A) -> A end; diff --git a/src/mod_muc_mnesia.erl b/src/mod_muc_mnesia.erl new file mode 100644 index 000000000..e3ae36978 --- /dev/null +++ b/src/mod_muc_mnesia.erl @@ -0,0 +1,163 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_muc_mnesia). + +-behaviour(mod_muc). + +%% API +-export([init/2, import/2, store_room/4, restore_room/3, forget_room/3, + can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4]). + +-include("mod_muc.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, Opts) -> + MyHost = proplists:get_value(host, Opts), + mnesia:create_table(muc_room, + [{disc_copies, [node()]}, + {attributes, + record_info(fields, muc_room)}]), + mnesia:create_table(muc_registered, + [{disc_copies, [node()]}, + {attributes, + record_info(fields, muc_registered)}]), + update_tables(MyHost), + mnesia:add_table_index(muc_registered, nick). + +store_room(_LServer, Host, Name, Opts) -> + F = fun () -> + mnesia:write(#muc_room{name_host = {Name, Host}, + opts = Opts}) + end, + mnesia:transaction(F). + +restore_room(_LServer, Host, Name) -> + case catch mnesia:dirty_read(muc_room, {Name, Host}) of + [#muc_room{opts = Opts}] -> Opts; + _ -> error + end. + +forget_room(_LServer, Host, Name) -> + F = fun () -> mnesia:delete({muc_room, {Name, Host}}) + end, + mnesia:transaction(F). + +can_use_nick(_LServer, Host, JID, Nick) -> + {LUser, LServer, _} = jid:tolower(JID), + LUS = {LUser, LServer}, + case catch mnesia:dirty_select(muc_registered, + [{#muc_registered{us_host = '$1', + nick = Nick, _ = '_'}, + [{'==', {element, 2, '$1'}, Host}], + ['$_']}]) + of + {'EXIT', _Reason} -> true; + [] -> true; + [#muc_registered{us_host = {U, _Host}}] -> U == LUS + end. + +get_rooms(_LServer, Host) -> + mnesia:dirty_select(muc_room, + [{#muc_room{name_host = {'_', Host}, + _ = '_'}, + [], ['$_']}]). + +get_nick(_LServer, Host, From) -> + {LUser, LServer, _} = jid:tolower(From), + LUS = {LUser, LServer}, + case mnesia:dirty_read(muc_registered, {LUS, Host}) of + [] -> error; + [#muc_registered{nick = Nick}] -> Nick + end. + +set_nick(_LServer, Host, From, Nick) -> + {LUser, LServer, _} = jid:tolower(From), + LUS = {LUser, LServer}, + F = fun () -> + case Nick of + <<"">> -> + mnesia:delete({muc_registered, {LUS, Host}}), + ok; + _ -> + Allow = case mnesia:select( + muc_registered, + [{#muc_registered{us_host = + '$1', + nick = Nick, + _ = '_'}, + [{'==', {element, 2, '$1'}, + Host}], + ['$_']}]) of + [] -> true; + [#muc_registered{us_host = {U, _Host}}] -> + U == LUS + end, + if Allow -> + mnesia:write(#muc_registered{ + us_host = {LUS, Host}, + nick = Nick}), + ok; + true -> + false + end + end + end, + mnesia:transaction(F). + +import(_LServer, #muc_room{} = R) -> + mnesia:dirty_write(R); +import(_LServer, #muc_registered{} = R) -> + mnesia:dirty_write(R). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +update_tables(Host) -> + update_muc_room_table(Host), + update_muc_registered_table(Host). + +update_muc_room_table(_Host) -> + Fields = record_info(fields, muc_room), + case mnesia:table_info(muc_room, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + muc_room, Fields, set, + fun(#muc_room{name_host = {N, _}}) -> N end, + fun(#muc_room{name_host = {N, H}, + opts = Opts} = R) -> + R#muc_room{name_host = {iolist_to_binary(N), + iolist_to_binary(H)}, + opts = mod_muc:opts_to_binary(Opts)} + end); + _ -> + ?INFO_MSG("Recreating muc_room table", []), + mnesia:transform_table(muc_room, ignore, Fields) + end. + +update_muc_registered_table(_Host) -> + Fields = record_info(fields, muc_registered), + case mnesia:table_info(muc_registered, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + muc_registered, Fields, set, + fun(#muc_registered{us_host = {_, H}}) -> H end, + fun(#muc_registered{us_host = {{U, S}, H}, + nick = Nick} = R) -> + R#muc_registered{us_host = {{iolist_to_binary(U), + iolist_to_binary(S)}, + iolist_to_binary(H)}, + nick = iolist_to_binary(Nick)} + end); + _ -> + ?INFO_MSG("Recreating muc_registered table", []), + mnesia:transform_table(muc_registered, ignore, Fields) + end. diff --git a/src/mod_muc_riak.erl b/src/mod_muc_riak.erl new file mode 100644 index 000000000..bc6e5959a --- /dev/null +++ b/src/mod_muc_riak.erl @@ -0,0 +1,117 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_muc_riak). + +-behaviour(mod_muc). + +%% API +-export([init/2, import/2, store_room/4, restore_room/3, forget_room/3, + can_use_nick/4, get_rooms/2, get_nick/3, set_nick/4]). + +-include("mod_muc.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +store_room(_LServer, Host, Name, Opts) -> + {atomic, ejabberd_riak:put(#muc_room{name_host = {Name, Host}, + opts = Opts}, + muc_room_schema())}. + +restore_room(_LServer, Host, Name) -> + case ejabberd_riak:get(muc_room, muc_room_schema(), {Name, Host}) of + {ok, #muc_room{opts = Opts}} -> Opts; + _ -> error + end. + +forget_room(_LServer, Host, Name) -> + {atomic, ejabberd_riak:delete(muc_room, {Name, Host})}. + +can_use_nick(LServer, Host, JID, Nick) -> + {LUser, LServer, _} = jid:tolower(JID), + LUS = {LUser, LServer}, + case ejabberd_riak:get_by_index(muc_registered, + muc_registered_schema(), + <<"nick_host">>, {Nick, Host}) of + {ok, []} -> + true; + {ok, [#muc_registered{us_host = {U, _Host}}]} -> + U == LUS; + {error, _} -> + true + end. + +get_rooms(_LServer, Host) -> + case ejabberd_riak:get(muc_room, muc_room_schema()) of + {ok, Rs} -> + lists:filter( + fun(#muc_room{name_host = {_, H}}) -> + Host == H + end, Rs); + _Err -> + [] + end. + +get_nick(LServer, Host, From) -> + {LUser, LServer, _} = jid:tolower(From), + US = {LUser, LServer}, + case ejabberd_riak:get(muc_registered, + muc_registered_schema(), + {US, Host}) of + {ok, #muc_registered{nick = Nick}} -> Nick; + {error, _} -> error + end. + +set_nick(LServer, Host, From, Nick) -> + {LUser, LServer, _} = jid:tolower(From), + LUS = {LUser, LServer}, + {atomic, + case Nick of + <<"">> -> + ejabberd_riak:delete(muc_registered, {LUS, Host}); + _ -> + Allow = case ejabberd_riak:get_by_index( + muc_registered, + muc_registered_schema(), + <<"nick_host">>, {Nick, Host}) of + {ok, []} -> + true; + {ok, [#muc_registered{us_host = {U, _Host}}]} -> + U == LUS; + {error, _} -> + false + end, + if Allow -> + ejabberd_riak:put(#muc_registered{us_host = {LUS, Host}, + nick = Nick}, + muc_registered_schema(), + [{'2i', [{<<"nick_host">>, + {Nick, Host}}]}]); + true -> + false + end + end}. + +import(_LServer, #muc_room{} = R) -> + ejabberd_riak:put(R, muc_room_schema()); +import(_LServer, #muc_registered{us_host = {_, Host}, nick = Nick} = R) -> + ejabberd_riak:put(R, muc_registered_schema(), + [{'2i', [{<<"nick_host">>, {Nick, Host}}]}]). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +muc_room_schema() -> + {record_info(fields, muc_room), #muc_room{}}. + +muc_registered_schema() -> + {record_info(fields, muc_registered), #muc_registered{}}. diff --git a/src/mod_muc_sql.erl b/src/mod_muc_sql.erl new file mode 100644 index 000000000..9acd9f8d7 --- /dev/null +++ b/src/mod_muc_sql.erl @@ -0,0 +1,202 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_muc_sql). + +-behaviour(mod_muc). + +%% API +-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/1, import/2, export/1]). + +-include("jlib.hrl"). +-include("mod_muc.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +store_room(LServer, Host, Name, Opts) -> + SName = ejabberd_odbc:escape(Name), + SHost = ejabberd_odbc:escape(Host), + SOpts = ejabberd_odbc:encode_term(Opts), + F = fun () -> + odbc_queries:update_t(<<"muc_room">>, + [<<"name">>, <<"host">>, <<"opts">>], + [SName, SHost, SOpts], + [<<"name='">>, SName, <<"' and host='">>, + SHost, <<"'">>]) + end, + ejabberd_odbc:sql_transaction(LServer, F). + +restore_room(LServer, Host, Name) -> + SName = ejabberd_odbc:escape(Name), + SHost = ejabberd_odbc:escape(Host), + case catch ejabberd_odbc:sql_query(LServer, + [<<"select opts from muc_room where name='">>, + SName, <<"' and host='">>, SHost, + <<"';">>]) of + {selected, [<<"opts">>], [[Opts]]} -> + mod_muc:opts_to_binary(ejabberd_odbc:decode_term(Opts)); + _ -> + error + end. + +forget_room(LServer, Host, Name) -> + SName = ejabberd_odbc:escape(Name), + SHost = ejabberd_odbc:escape(Host), + F = fun () -> + ejabberd_odbc:sql_query_t([<<"delete from muc_room where name='">>, + SName, <<"' and host='">>, SHost, + <<"';">>]) + end, + ejabberd_odbc:sql_transaction(LServer, F). + +can_use_nick(LServer, Host, JID, Nick) -> + SJID = jid:to_string(jid:tolower(jid:remove_resource(JID))), + SNick = ejabberd_odbc:escape(Nick), + SHost = ejabberd_odbc:escape(Host), + case catch ejabberd_odbc:sql_query(LServer, + [<<"select jid from muc_registered ">>, + <<"where nick='">>, SNick, + <<"' and host='">>, SHost, <<"';">>]) of + {selected, [<<"jid">>], [[SJID1]]} -> SJID == SJID1; + _ -> true + end. + +get_rooms(LServer, Host) -> + SHost = ejabberd_odbc:escape(Host), + case catch ejabberd_odbc:sql_query(LServer, + [<<"select name, opts from muc_room ">>, + <<"where host='">>, SHost, <<"';">>]) of + {selected, [<<"name">>, <<"opts">>], RoomOpts} -> + lists:map( + fun([Room, Opts]) -> + #muc_room{name_host = {Room, Host}, + opts = mod_muc:opts_to_binary( + ejabberd_odbc:decode_term(Opts))} + end, RoomOpts); + Err -> + ?ERROR_MSG("failed to get rooms: ~p", [Err]), + [] + end. + +get_nick(LServer, Host, From) -> + SJID = ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))), + SHost = ejabberd_odbc:escape(Host), + case catch ejabberd_odbc:sql_query(LServer, + [<<"select nick from muc_registered where " + "jid='">>, + SJID, <<"' and host='">>, SHost, + <<"';">>]) of + {selected, [<<"nick">>], [[Nick]]} -> Nick; + _ -> error + end. + +set_nick(LServer, Host, From, Nick) -> + JID = jid:to_string(jid:tolower(jid:remove_resource(From))), + SJID = ejabberd_odbc:escape(JID), + SNick = ejabberd_odbc:escape(Nick), + SHost = ejabberd_odbc:escape(Host), + F = fun () -> + case Nick of + <<"">> -> + ejabberd_odbc:sql_query_t( + [<<"delete from muc_registered where ">>, + <<"jid='">>, SJID, + <<"' and host='">>, Host, + <<"';">>]), + ok; + _ -> + Allow = case ejabberd_odbc:sql_query_t( + [<<"select jid from muc_registered ">>, + <<"where nick='">>, + SNick, + <<"' and host='">>, + SHost, <<"';">>]) of + {selected, [<<"jid">>], [[J]]} -> J == JID; + _ -> true + end, + if Allow -> + odbc_queries:update_t(<<"muc_registered">>, + [<<"jid">>, <<"host">>, + <<"nick">>], + [SJID, SHost, SNick], + [<<"jid='">>, SJID, + <<"' and host='">>, SHost, + <<"'">>]), + ok; + true -> + false + end + end + end, + ejabberd_odbc:sql_transaction(LServer, F). + +export(_Server) -> + [{muc_room, + fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) -> + case str:suffix(Host, RoomHost) of + true -> + SName = ejabberd_odbc:escape(Name), + SRoomHost = ejabberd_odbc:escape(RoomHost), + SOpts = ejabberd_odbc:encode_term(Opts), + [[<<"delete from muc_room where name='">>, SName, + <<"' and host='">>, SRoomHost, <<"';">>], + [<<"insert into muc_room(name, host, opts) ", + "values (">>, + <<"'">>, SName, <<"', '">>, SRoomHost, + <<"', '">>, SOpts, <<"');">>]]; + false -> + [] + end + end}, + {muc_registered, + fun(Host, #muc_registered{us_host = {{U, S}, RoomHost}, + nick = Nick}) -> + case str:suffix(Host, RoomHost) of + true -> + SJID = ejabberd_odbc:escape( + jid:to_string( + jid:make(U, S, <<"">>))), + SNick = ejabberd_odbc:escape(Nick), + SRoomHost = ejabberd_odbc:escape(RoomHost), + [[<<"delete from muc_registered where jid='">>, + SJID, <<"' and host='">>, SRoomHost, <<"';">>], + [<<"insert into muc_registered(jid, host, " + "nick) values ('">>, + SJID, <<"', '">>, SRoomHost, <<"', '">>, SNick, + <<"');">>]]; + false -> + [] + end + end}]. + +import(_LServer) -> + [{<<"select name, host, opts from muc_room;">>, + fun([Name, RoomHost, SOpts]) -> + Opts = mod_muc:opts_to_binary(ejabberd_odbc:decode_term(SOpts)), + #muc_room{name_host = {Name, RoomHost}, opts = Opts} + end}, + {<<"select jid, host, nick from muc_registered;">>, + fun([J, RoomHost, Nick]) -> + #jid{user = U, server = S} = jid:from_string(J), + #muc_registered{us_host = {{U, S}, RoomHost}, + nick = Nick} + end}]. + +import(_, _) -> + pass. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== From 398f1de5aecc574d65f8f3308fe3cba037ad8a99 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 14 Apr 2016 10:58:32 +0300 Subject: [PATCH 09/15] Clean mod_roster.erl from DB specific code --- src/mod_roster.erl | 651 ++++---------------------------------- src/mod_roster_mnesia.erl | 171 ++++++++++ src/mod_roster_riak.erl | 113 +++++++ src/mod_roster_sql.erl | 308 ++++++++++++++++++ 4 files changed, 657 insertions(+), 586 deletions(-) create mode 100644 src/mod_roster_mnesia.erl create mode 100644 src/mod_roster_riak.erl create mode 100644 src/mod_roster_sql.erl diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 35072e5f8..16354dd8f 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -49,7 +49,6 @@ get_jid_info/4, item_to_xml/1, webadmin_page/3, webadmin_user/4, get_versioning_feature/2, roster_versioning_enabled/1, roster_version/2, - record_to_string/1, groups_to_string/1, mod_opt_type/1, set_roster/1]). -include("ejabberd.hrl"). @@ -65,23 +64,27 @@ -export_type([subscription/0]). +-callback init(binary(), gen_mod:opts()) -> any(). +-callback import(binary(), #roster{} | #roster_version{}) -> ok | pass. +-callback read_roster_version(binary(), binary()) -> binary() | error. +-callback write_roster_version(binary(), binary(), boolean(), binary()) -> any(). +-callback get_roster(binary(), binary()) -> [#roster{}]. +-callback get_roster_by_jid(binary(), binary(), ljid()) -> #roster{}. +-callback get_only_items(binary(), binary()) -> [#roster{}]. +-callback roster_subscribe(binary(), binary(), ljid(), #roster{}) -> any(). +-callback transaction(binary(), function()) -> {atomic, any()} | {aborted, any()}. +-callback get_roster_by_jid_with_groups(binary(), binary(), ljid()) -> #roster{}. +-callback remove_user(binary(), binary()) -> {atomic, any()}. +-callback update_roster(binary(), binary(), ljid(), #roster{}) -> any(). +-callback del_roster(binary(), binary(), ljid()) -> any(). +-callback read_subscription_and_groups(binary(), binary(), ljid()) -> + {subscription(), [binary()]}. + start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, one_queue), - case gen_mod:db_type(Host, Opts) of - mnesia -> - mnesia:create_table(roster, - [{disc_copies, [node()]}, - {attributes, record_info(fields, roster)}]), - mnesia:create_table(roster_version, - [{disc_copies, [node()]}, - {attributes, - record_info(fields, roster_version)}]), - update_tables(), - mnesia:add_table_index(roster, us), - mnesia:add_table_index(roster_version, us); - _ -> ok - end, + Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod:init(Host, Opts), ejabberd_hooks:add(roster_get, Host, ?MODULE, get_user_roster, 50), ejabberd_hooks:add(roster_in_subscription, Host, @@ -194,26 +197,8 @@ roster_version(LServer, LUser) -> end. read_roster_version(LUser, LServer) -> - read_roster_version(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). - -read_roster_version(LUser, LServer, mnesia) -> - US = {LUser, LServer}, - case mnesia:dirty_read(roster_version, US) of - [#roster_version{version = V}] -> V; - [] -> error - end; -read_roster_version(LUser, LServer, odbc) -> - case odbc_queries:get_roster_version(LServer, LUser) of - {selected, [{Version}]} -> Version; - {selected, []} -> error - end; -read_roster_version(LServer, LUser, riak) -> - case ejabberd_riak:get(roster_version, roster_version_schema(), - {LUser, LServer}) of - {ok, #roster_version{version = V}} -> V; - _Err -> error - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:read_roster_version(LUser, LServer). write_roster_version(LUser, LServer) -> write_roster_version(LUser, LServer, false). @@ -223,38 +208,10 @@ write_roster_version_t(LUser, LServer) -> write_roster_version(LUser, LServer, InTransaction) -> Ver = p1_sha:sha(term_to_binary(p1_time_compat:unique_integer())), - write_roster_version(LUser, LServer, InTransaction, Ver, - gen_mod:db_type(LServer, ?MODULE)), + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:write_roster_version(LUser, LServer, InTransaction, Ver), Ver. -write_roster_version(LUser, LServer, InTransaction, Ver, - mnesia) -> - US = {LUser, LServer}, - if InTransaction -> - mnesia:write(#roster_version{us = US, version = Ver}); - true -> - mnesia:dirty_write(#roster_version{us = US, - version = Ver}) - end; -write_roster_version(LUser, LServer, InTransaction, Ver, - odbc) -> - Username = ejabberd_odbc:escape(LUser), - EVer = ejabberd_odbc:escape(Ver), - if InTransaction -> - odbc_queries:set_roster_version(Username, EVer); - true -> - odbc_queries:sql_transaction(LServer, - fun () -> - odbc_queries:set_roster_version(Username, - EVer) - end) - end; -write_roster_version(LUser, LServer, _InTransaction, Ver, - riak) -> - US = {LUser, LServer}, - ejabberd_riak:put(#roster_version{us = US, version = Ver}, - roster_version_schema()). - %% Load roster from DB only if neccesary. %% It is neccesary if %% - roster versioning is disabled in server OR @@ -350,56 +307,8 @@ get_user_roster(Acc, {LUser, LServer}) -> ++ Acc. get_roster(LUser, LServer) -> - get_roster(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). - -get_roster(LUser, LServer, mnesia) -> - US = {LUser, LServer}, - case catch mnesia:dirty_index_read(roster, US, - #roster.us) - of - Items when is_list(Items)-> Items; - _ -> [] - end; -get_roster(LUser, LServer, riak) -> - case ejabberd_riak:get_by_index(roster, roster_schema(), - <<"us">>, {LUser, LServer}) of - {ok, Items} -> Items; - _Err -> [] - end; -get_roster(LUser, LServer, odbc) -> - case catch odbc_queries:get_roster(LServer, LUser) of - {selected, Items} when is_list(Items) -> - JIDGroups = case catch odbc_queries:get_roster_jid_groups( - LServer, LUser) of - {selected, JGrps} - when is_list(JGrps) -> - JGrps; - _ -> [] - end, - GroupsDict = lists:foldl(fun({J, G}, Acc) -> - dict:append(J, G, Acc) - end, - dict:new(), JIDGroups), - RItems = - lists:flatmap( - fun(I) -> - case raw_to_record(LServer, I) of - %% Bad JID in database: - error -> []; - R -> - SJID = jid:to_string(R#roster.jid), - Groups = case dict:find(SJID, GroupsDict) of - {ok, Gs} -> Gs; - error -> [] - end, - [R#roster{groups = Groups}] - end - end, - Items), - RItems; - _ -> [] - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:get_roster(LUser, LServer). set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) -> transaction( @@ -437,48 +346,8 @@ item_to_xml(Item) -> children = SubEls}. get_roster_by_jid_t(LUser, LServer, LJID) -> - DBType = gen_mod:db_type(LServer, ?MODULE), - get_roster_by_jid_t(LUser, LServer, LJID, DBType). - -get_roster_by_jid_t(LUser, LServer, LJID, mnesia) -> - case mnesia:read({roster, {LUser, LServer, LJID}}) of - [] -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID}; - [I] -> - I#roster{jid = LJID, name = <<"">>, groups = [], - xs = []} - end; -get_roster_by_jid_t(LUser, LServer, LJID, odbc) -> - {selected, Res} = - odbc_queries:get_roster_by_jid(LServer, LUser, jid:to_string(LJID)), - case Res of - [] -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID}; - [I] -> - R = raw_to_record(LServer, I), - case R of - %% Bad JID in database: - error -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID}; - _ -> - R#roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID, name = <<"">>} - end - end; -get_roster_by_jid_t(LUser, LServer, LJID, riak) -> - case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of - {ok, I} -> - I#roster{jid = LJID, name = <<"">>, groups = [], - xs = []}; - {error, notfound} -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID}; - Err -> - exit(Err) - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:get_roster_by_jid(LUser, LServer, LJID). try_process_iq_set(From, To, #iq{sub_el = SubEl, lang = Lang} = IQ) -> #jid{server = Server} = From, @@ -632,77 +501,37 @@ push_item_version(Server, User, From, Item, end, ejabberd_sm:get_user_resources(User, Server)). -get_subscription_lists(Acc, User, Server) -> +get_subscription_lists(_Acc, User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - DBType = gen_mod:db_type(LServer, ?MODULE), - Items = get_subscription_lists(Acc, LUser, LServer, - DBType), + Mod = gen_mod:db_mod(LServer, ?MODULE), + Items = Mod:get_only_items(LUser, LServer), fill_subscription_lists(LServer, Items, [], []). -get_subscription_lists(_, LUser, LServer, mnesia) -> - US = {LUser, LServer}, - case mnesia:dirty_index_read(roster, US, #roster.us) of - Items when is_list(Items) -> Items; - _ -> [] - end; -get_subscription_lists(_, LUser, LServer, odbc) -> - case catch odbc_queries:get_roster(LServer, LUser) of - {selected, Items} when is_list(Items) -> - lists:map(fun(I) -> raw_to_record(LServer, I) end, Items); - _ -> [] - end; -get_subscription_lists(_, LUser, LServer, riak) -> - case ejabberd_riak:get_by_index(roster, roster_schema(), - <<"us">>, {LUser, LServer}) of - {ok, Items} -> Items; - _Err -> [] - end. - -fill_subscription_lists(LServer, [#roster{} = I | Is], - F, T) -> +fill_subscription_lists(LServer, [I | Is], F, T) -> J = element(3, I#roster.usj), case I#roster.subscription of - both -> - fill_subscription_lists(LServer, Is, [J | F], [J | T]); - from -> - fill_subscription_lists(LServer, Is, [J | F], T); - to -> fill_subscription_lists(LServer, Is, F, [J | T]); - _ -> fill_subscription_lists(LServer, Is, F, T) + both -> + fill_subscription_lists(LServer, Is, [J | F], [J | T]); + from -> + fill_subscription_lists(LServer, Is, [J | F], T); + to -> fill_subscription_lists(LServer, Is, F, [J | T]); + _ -> fill_subscription_lists(LServer, Is, F, T) end; -fill_subscription_lists(LServer, [RawI | Is], F, T) -> - I = raw_to_record(LServer, RawI), - case I of - %% Bad JID in database: - error -> fill_subscription_lists(LServer, Is, F, T); - _ -> fill_subscription_lists(LServer, [I | Is], F, T) - end; -fill_subscription_lists(_LServer, [], F, T) -> {F, T}. +fill_subscription_lists(_LServer, [], F, T) -> + {F, T}. ask_to_pending(subscribe) -> out; ask_to_pending(unsubscribe) -> none; ask_to_pending(Ask) -> Ask. roster_subscribe_t(LUser, LServer, LJID, Item) -> - DBType = gen_mod:db_type(LServer, ?MODULE), - roster_subscribe_t(LUser, LServer, LJID, Item, DBType). - -roster_subscribe_t(_LUser, _LServer, _LJID, Item, - mnesia) -> - mnesia:write(Item); -roster_subscribe_t(_LUser, _LServer, _LJID, Item, odbc) -> - ItemVals = record_to_row(Item), - odbc_queries:roster_subscribe(ItemVals); -roster_subscribe_t(LUser, LServer, _LJID, Item, riak) -> - ejabberd_riak:put(Item, roster_schema(), - [{'2i', [{<<"us">>, {LUser, LServer}}]}]). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:roster_subscribe(LUser, LServer, LJID, Item). transaction(LServer, F) -> - case gen_mod:db_type(LServer, ?MODULE) of - mnesia -> mnesia:transaction(F); - odbc -> ejabberd_odbc:sql_transaction(LServer, F); - riak -> {atomic, F()} - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:transaction(LServer, F). in_subscription(_, User, Server, JID, Type, Reason) -> process_subscription(in, User, Server, JID, Type, @@ -712,45 +541,8 @@ out_subscription(User, Server, JID, Type) -> process_subscription(out, User, Server, JID, Type, <<"">>). get_roster_by_jid_with_groups_t(LUser, LServer, LJID) -> - DBType = gen_mod:db_type(LServer, ?MODULE), - get_roster_by_jid_with_groups_t(LUser, LServer, LJID, - DBType). - -get_roster_by_jid_with_groups_t(LUser, LServer, LJID, - mnesia) -> - case mnesia:read({roster, {LUser, LServer, LJID}}) of - [] -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID}; - [I] -> I - end; -get_roster_by_jid_with_groups_t(LUser, LServer, LJID, - odbc) -> - SJID = jid:to_string(LJID), - case odbc_queries:get_roster_by_jid(LServer, LUser, SJID) of - {selected, [I]} -> - R = raw_to_record(LServer, I), - Groups = - case odbc_queries:get_roster_groups(LServer, LUser, SJID) of - {selected, JGrps} when is_list(JGrps) -> - [JGrp || {JGrp} <- JGrps]; - _ -> [] - end, - R#roster{groups = Groups}; - {selected, []} -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID} - end; -get_roster_by_jid_with_groups_t(LUser, LServer, LJID, riak) -> - case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of - {ok, I} -> - I; - {error, notfound} -> - #roster{usj = {LUser, LServer, LJID}, - us = {LUser, LServer}, jid = LJID}; - Err -> - exit(Err) - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:get_roster_by_jid_with_groups(LUser, LServer, LJID). process_subscription(Direction, User, Server, JID1, Type, Reason) -> @@ -948,21 +740,8 @@ remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), send_unsubscription_to_rosteritems(LUser, LServer), - remove_user(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). - -remove_user(LUser, LServer, mnesia) -> - US = {LUser, LServer}, - F = fun () -> - lists:foreach(fun (R) -> mnesia:delete_object(R) end, - mnesia:index_read(roster, US, #roster.us)) - end, - mnesia:transaction(F); -remove_user(LUser, LServer, odbc) -> - odbc_queries:del_user_roster_t(LServer, LUser), - ok; -remove_user(LUser, LServer, riak) -> - {atomic, ejabberd_riak:delete_by_index(roster, <<"us">>, {LUser, LServer})}. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:remove_user(LUser, LServer). %% For each contact with Subscription: %% Both or From, send a "unsubscribed" presence stanza; @@ -1020,33 +799,12 @@ set_items(User, Server, SubEl) -> transaction(LServer, F). update_roster_t(LUser, LServer, LJID, Item) -> - DBType = gen_mod:db_type(LServer, ?MODULE), - update_roster_t(LUser, LServer, LJID, Item, DBType). - -update_roster_t(_LUser, _LServer, _LJID, Item, - mnesia) -> - mnesia:write(Item); -update_roster_t(LUser, LServer, LJID, Item, odbc) -> - SJID = jid:to_string(LJID), - ItemVals = record_to_row(Item), - ItemGroups = Item#roster.groups, - odbc_queries:update_roster(LServer, LUser, SJID, ItemVals, - ItemGroups); -update_roster_t(LUser, LServer, _LJID, Item, riak) -> - ejabberd_riak:put(Item, roster_schema(), - [{'2i', [{<<"us">>, {LUser, LServer}}]}]). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:update_roster(LUser, LServer, LJID, Item). del_roster_t(LUser, LServer, LJID) -> - DBType = gen_mod:db_type(LServer, ?MODULE), - del_roster_t(LUser, LServer, LJID, DBType). - -del_roster_t(LUser, LServer, LJID, mnesia) -> - mnesia:delete({roster, {LUser, LServer, LJID}}); -del_roster_t(LUser, LServer, LJID, odbc) -> - SJID = jid:to_string(LJID), - odbc_queries:del_roster(LServer, LUser, SJID); -del_roster_t(LUser, LServer, LJID, riak) -> - ejabberd_riak:delete(roster, {LUser, LServer, LJID}). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:del_roster(LUser, LServer, LJID). process_item_set_t(LUser, LServer, #xmlel{attrs = Attrs, children = Els}) -> @@ -1109,13 +867,12 @@ process_item_attrs_ws(Item, []) -> Item. get_in_pending_subscriptions(Ls, User, Server) -> LServer = jid:nameprep(Server), - get_in_pending_subscriptions(Ls, User, Server, - gen_mod:db_type(LServer, ?MODULE)). + Mod = gen_mod:db_mod(LServer, ?MODULE), + get_in_pending_subscriptions(Ls, User, Server, Mod). -get_in_pending_subscriptions(Ls, User, Server, DBType) - when DBType == mnesia; DBType == riak -> +get_in_pending_subscriptions(Ls, User, Server, Mod) -> JID = jid:make(User, Server, <<"">>), - Result = get_roster(JID#jid.luser, JID#jid.lserver, DBType), + Result = Mod:get_only_items(JID#jid.luser, JID#jid.lserver), Ls ++ lists:map(fun (R) -> Message = R#roster.askmessage, Status = if is_binary(Message) -> (Message); @@ -1140,93 +897,15 @@ get_in_pending_subscriptions(Ls, User, Server, DBType) _ -> false end end, - Result)); -get_in_pending_subscriptions(Ls, User, Server, odbc) -> - JID = jid:make(User, Server, <<"">>), - LUser = JID#jid.luser, - LServer = JID#jid.lserver, - case catch odbc_queries:get_roster(LServer, LUser) of - {selected, Items} when is_list(Items) -> - Ls ++ - lists:map(fun (R) -> - Message = R#roster.askmessage, - #xmlel{name = <<"presence">>, - attrs = - [{<<"from">>, - jid:to_string(R#roster.jid)}, - {<<"to">>, jid:to_string(JID)}, - {<<"type">>, <<"subscribe">>}], - children = - [#xmlel{name = <<"status">>, - attrs = [], - children = - [{xmlcdata, Message}]}]} - end, - lists:flatmap(fun (I) -> - case raw_to_record(LServer, I) of - %% Bad JID in database: - error -> []; - R -> - case R#roster.ask of - in -> [R]; - both -> [R]; - _ -> [] - end - end - end, - Items)); - _ -> Ls - end. + Result)). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% read_subscription_and_groups(User, Server, LJID) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - read_subscription_and_groups(LUser, LServer, LJID, - gen_mod:db_type(LServer, ?MODULE)). - -read_subscription_and_groups(LUser, LServer, LJID, - mnesia) -> - case catch mnesia:dirty_read(roster, - {LUser, LServer, LJID}) - of - [#roster{subscription = Subscription, - groups = Groups}] -> - {Subscription, Groups}; - _ -> error - end; -read_subscription_and_groups(LUser, LServer, LJID, - odbc) -> - SJID = jid:to_string(LJID), - case catch odbc_queries:get_subscription(LServer, LUser, SJID) of - {selected, [{SSubscription}]} -> - Subscription = case SSubscription of - <<"B">> -> both; - <<"T">> -> to; - <<"F">> -> from; - _ -> none - end, - Groups = case catch - odbc_queries:get_rostergroup_by_jid(LServer, LUser, - SJID) - of - {selected, JGrps} when is_list(JGrps) -> - [JGrp || {JGrp} <- JGrps]; - _ -> [] - end, - {Subscription, Groups}; - _ -> error - end; -read_subscription_and_groups(LUser, LServer, LJID, - riak) -> - case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of - {ok, #roster{subscription = Subscription, - groups = Groups}} -> - {Subscription, Groups}; - _ -> - error - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:read_subscription_and_groups(LUser, LServer, LJID). get_jid_info(_, User, Server, JID) -> LJID = jid:tolower(JID), @@ -1246,155 +925,6 @@ get_jid_info(_, User, Server, JID) -> %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -raw_to_record(LServer, - [User, SJID, Nick, SSubscription, SAsk, SAskMessage, - _SServer, _SSubscribe, _SType]) -> - raw_to_record(LServer, - {User, SJID, Nick, SSubscription, SAsk, SAskMessage, - _SServer, _SSubscribe, _SType}); -raw_to_record(LServer, - {User, SJID, Nick, SSubscription, SAsk, SAskMessage, - _SServer, _SSubscribe, _SType}) -> - case jid:from_string(SJID) of - error -> error; - JID -> - LJID = jid:tolower(JID), - Subscription = case SSubscription of - <<"B">> -> both; - <<"T">> -> to; - <<"F">> -> from; - _ -> none - end, - Ask = case SAsk of - <<"S">> -> subscribe; - <<"U">> -> unsubscribe; - <<"B">> -> both; - <<"O">> -> out; - <<"I">> -> in; - _ -> none - end, - #roster{usj = {User, LServer, LJID}, - us = {User, LServer}, jid = LJID, name = Nick, - subscription = Subscription, ask = Ask, - askmessage = SAskMessage} - end. - -record_to_string(#roster{us = {User, _Server}, - jid = JID, name = Name, subscription = Subscription, - ask = Ask, askmessage = AskMessage}) -> - Username = ejabberd_odbc:escape(User), - SJID = - ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))), - Nick = ejabberd_odbc:escape(Name), - SSubscription = case Subscription of - both -> <<"B">>; - to -> <<"T">>; - from -> <<"F">>; - none -> <<"N">> - end, - SAsk = case Ask of - subscribe -> <<"S">>; - unsubscribe -> <<"U">>; - both -> <<"B">>; - out -> <<"O">>; - in -> <<"I">>; - none -> <<"N">> - end, - SAskMessage = ejabberd_odbc:escape(AskMessage), - [Username, SJID, Nick, SSubscription, SAsk, SAskMessage, - <<"N">>, <<"">>, <<"item">>]. - -record_to_row( - #roster{us = {LUser, _LServer}, - jid = JID, name = Name, subscription = Subscription, - ask = Ask, askmessage = AskMessage}) -> - SJID = jid:to_string(jid:tolower(JID)), - SSubscription = case Subscription of - both -> <<"B">>; - to -> <<"T">>; - from -> <<"F">>; - none -> <<"N">> - end, - SAsk = case Ask of - subscribe -> <<"S">>; - unsubscribe -> <<"U">>; - both -> <<"B">>; - out -> <<"O">>; - in -> <<"I">>; - none -> <<"N">> - end, - {LUser, SJID, Name, SSubscription, SAsk, AskMessage}. - -groups_to_string(#roster{us = {User, _Server}, - jid = JID, groups = Groups}) -> - Username = ejabberd_odbc:escape(User), - SJID = - ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))), - lists:foldl(fun (<<"">>, Acc) -> Acc; - (Group, Acc) -> - G = ejabberd_odbc:escape(Group), - [[Username, SJID, G] | Acc] - end, - [], Groups). - -update_tables() -> - update_roster_table(), - update_roster_version_table(). - -update_roster_table() -> - Fields = record_info(fields, roster), - case mnesia:table_info(roster, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - roster, Fields, set, - fun(#roster{usj = {U, _, _}}) -> U end, - fun(#roster{usj = {U, S, {LU, LS, LR}}, - us = {U1, S1}, - jid = {U2, S2, R2}, - name = Name, - groups = Gs, - askmessage = Ask, - xs = Xs} = R) -> - R#roster{usj = {iolist_to_binary(U), - iolist_to_binary(S), - {iolist_to_binary(LU), - iolist_to_binary(LS), - iolist_to_binary(LR)}}, - us = {iolist_to_binary(U1), - iolist_to_binary(S1)}, - jid = {iolist_to_binary(U2), - iolist_to_binary(S2), - iolist_to_binary(R2)}, - name = iolist_to_binary(Name), - groups = [iolist_to_binary(G) || G <- Gs], - askmessage = try iolist_to_binary(Ask) - catch _:_ -> <<"">> end, - xs = [fxml:to_xmlel(X) || X <- Xs]} - end); - _ -> - ?INFO_MSG("Recreating roster table", []), - mnesia:transform_table(roster, ignore, Fields) - end. - -%% Convert roster table to support virtual host -%% Convert roster table: xattrs fields become -update_roster_version_table() -> - Fields = record_info(fields, roster_version), - case mnesia:table_info(roster_version, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - roster_version, Fields, set, - fun(#roster_version{us = {U, _}}) -> U end, - fun(#roster_version{us = {U, S}, version = Ver} = R) -> - R#roster_version{us = {iolist_to_binary(U), - iolist_to_binary(S)}, - version = iolist_to_binary(Ver)} - end); - _ -> - ?INFO_MSG("Recreating roster_version table", []), - mnesia:transform_table(roster_version, ignore, Fields) - end. - webadmin_page(_, Host, #request{us = _US, path = [<<"user">>, U, <<"roster">>], q = Query, lang = Lang} = @@ -1692,68 +1222,17 @@ is_managed_from_id(<<"roster-remotely-managed">>) -> is_managed_from_id(_Id) -> false. -roster_schema() -> - {record_info(fields, roster), #roster{}}. - -roster_version_schema() -> - {record_info(fields, roster_version), #roster_version{}}. - -export(_Server) -> - [{roster, - fun(Host, #roster{usj = {LUser, LServer, LJID}} = R) - when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - SJID = ejabberd_odbc:escape(jid:to_string(LJID)), - ItemVals = record_to_string(R), - ItemGroups = groups_to_string(R), - odbc_queries:update_roster_sql(Username, SJID, - ItemVals, ItemGroups); - (_Host, _R) -> - [] - end}, - {roster_version, - fun(Host, #roster_version{us = {LUser, LServer}, version = Ver}) - when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - SVer = ejabberd_odbc:escape(Ver), - [[<<"delete from roster_version where username='">>, - Username, <<"';">>], - [<<"insert into roster_version(username, version) values('">>, - Username, <<"', '">>, SVer, <<"');">>]]; - (_Host, _R) -> - [] - end}]. +export(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:export(LServer). import(LServer) -> - [{<<"select username, jid, nick, subscription, " - "ask, askmessage, server, subscribe, type from rosterusers;">>, - fun([LUser, JID|_] = Row) -> - Item = raw_to_record(LServer, Row), - Username = ejabberd_odbc:escape(LUser), - SJID = ejabberd_odbc:escape(JID), - {selected, _, Rows} = - ejabberd_odbc:sql_query_t( - [<<"select grp from rostergroups where username='">>, - Username, <<"' and jid='">>, SJID, <<"'">>]), - Groups = [Grp || [Grp] <- Rows], - Item#roster{groups = Groups} - end}, - {<<"select username, version from roster_version;">>, - fun([LUser, Ver]) -> - #roster_version{us = {LUser, LServer}, version = Ver} - end}]. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:import(LServer). -import(_LServer, mnesia, #roster{} = R) -> - mnesia:dirty_write(R); -import(_LServer, mnesia, #roster_version{} = RV) -> - mnesia:dirty_write(RV); -import(_LServer, riak, #roster{us = {LUser, LServer}} = R) -> - ejabberd_riak:put(R, roster_schema(), - [{'2i', [{<<"us">>, {LUser, LServer}}]}]); -import(_LServer, riak, #roster_version{} = RV) -> - ejabberd_riak:put(RV, roster_version_schema()); -import(_, _, _) -> - pass. +import(LServer, DBType, R) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, R). mod_opt_type(access) -> fun (A) when is_atom(A) -> A end; diff --git a/src/mod_roster_mnesia.erl b/src/mod_roster_mnesia.erl new file mode 100644 index 000000000..ddfa34d68 --- /dev/null +++ b/src/mod_roster_mnesia.erl @@ -0,0 +1,171 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 13 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_roster_mnesia). + +-behaviour(mod_roster). + +%% API +-export([init/2, read_roster_version/2, write_roster_version/4, + get_roster/2, get_roster_by_jid/3, get_only_items/2, + roster_subscribe/4, get_roster_by_jid_with_groups/3, + remove_user/2, update_roster/4, del_roster/3, transaction/2, + read_subscription_and_groups/3, import/2]). + +-include("jlib.hrl"). +-include("mod_roster.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + mnesia:create_table(roster, + [{disc_copies, [node()]}, + {attributes, record_info(fields, roster)}]), + mnesia:create_table(roster_version, + [{disc_copies, [node()]}, + {attributes, + record_info(fields, roster_version)}]), + update_tables(), + mnesia:add_table_index(roster, us), + mnesia:add_table_index(roster_version, us). + +read_roster_version(LUser, LServer) -> + US = {LUser, LServer}, + case mnesia:dirty_read(roster_version, US) of + [#roster_version{version = V}] -> V; + [] -> error + end. + +write_roster_version(LUser, LServer, InTransaction, Ver) -> + US = {LUser, LServer}, + if InTransaction -> + mnesia:write(#roster_version{us = US, version = Ver}); + true -> + mnesia:dirty_write(#roster_version{us = US, version = Ver}) + end. + +get_roster(LUser, LServer) -> + mnesia:dirty_index_read(roster, {LUser, LServer}, #roster.us). + +get_roster_by_jid(LUser, LServer, LJID) -> + case mnesia:read({roster, {LUser, LServer, LJID}}) of + [] -> + #roster{usj = {LUser, LServer, LJID}, + us = {LUser, LServer}, jid = LJID}; + [I] -> + I#roster{jid = LJID, name = <<"">>, groups = [], + xs = []} + end. + +get_only_items(LUser, LServer) -> + get_roster(LUser, LServer). + +roster_subscribe(_LUser, _LServer, _LJID, Item) -> + mnesia:write(Item). + +get_roster_by_jid_with_groups(LUser, LServer, LJID) -> + case mnesia:read({roster, {LUser, LServer, LJID}}) of + [] -> + #roster{usj = {LUser, LServer, LJID}, + us = {LUser, LServer}, jid = LJID}; + [I] -> I + end. + +remove_user(LUser, LServer) -> + US = {LUser, LServer}, + F = fun () -> + lists:foreach( + fun (R) -> mnesia:delete_object(R) end, + mnesia:index_read(roster, US, #roster.us)) + end, + mnesia:transaction(F). + +update_roster(_LUser, _LServer, _LJID, Item) -> + mnesia:write(Item). + +del_roster(LUser, LServer, LJID) -> + mnesia:delete({roster, {LUser, LServer, LJID}}). + +read_subscription_and_groups(LUser, LServer, LJID) -> + case mnesia:dirty_read(roster, {LUser, LServer, LJID}) of + [#roster{subscription = Subscription, groups = Groups}] -> + {Subscription, Groups}; + _ -> + error + end. + +transaction(_LServer, F) -> + mnesia:transaction(F). + +import(_LServer, #roster{} = R) -> + mnesia:dirty_write(R); +import(_LServer, #roster_version{} = RV) -> + mnesia:dirty_write(RV). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +update_tables() -> + update_roster_table(), + update_roster_version_table(). + +update_roster_table() -> + Fields = record_info(fields, roster), + case mnesia:table_info(roster, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + roster, Fields, set, + fun(#roster{usj = {U, _, _}}) -> U end, + fun(#roster{usj = {U, S, {LU, LS, LR}}, + us = {U1, S1}, + jid = {U2, S2, R2}, + name = Name, + groups = Gs, + askmessage = Ask, + xs = Xs} = R) -> + R#roster{usj = {iolist_to_binary(U), + iolist_to_binary(S), + {iolist_to_binary(LU), + iolist_to_binary(LS), + iolist_to_binary(LR)}}, + us = {iolist_to_binary(U1), + iolist_to_binary(S1)}, + jid = {iolist_to_binary(U2), + iolist_to_binary(S2), + iolist_to_binary(R2)}, + name = iolist_to_binary(Name), + groups = [iolist_to_binary(G) || G <- Gs], + askmessage = try iolist_to_binary(Ask) + catch _:_ -> <<"">> end, + xs = [fxml:to_xmlel(X) || X <- Xs]} + end); + _ -> + ?INFO_MSG("Recreating roster table", []), + mnesia:transform_table(roster, ignore, Fields) + end. + +%% Convert roster table to support virtual host +%% Convert roster table: xattrs fields become +update_roster_version_table() -> + Fields = record_info(fields, roster_version), + case mnesia:table_info(roster_version, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + roster_version, Fields, set, + fun(#roster_version{us = {U, _}}) -> U end, + fun(#roster_version{us = {U, S}, version = Ver} = R) -> + R#roster_version{us = {iolist_to_binary(U), + iolist_to_binary(S)}, + version = iolist_to_binary(Ver)} + end); + _ -> + ?INFO_MSG("Recreating roster_version table", []), + mnesia:transform_table(roster_version, ignore, Fields) + end. diff --git a/src/mod_roster_riak.erl b/src/mod_roster_riak.erl new file mode 100644 index 000000000..38e873827 --- /dev/null +++ b/src/mod_roster_riak.erl @@ -0,0 +1,113 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_roster_riak). + + +-behaviour(mod_roster). + +%% API +-export([init/2, read_roster_version/2, write_roster_version/4, + get_roster/2, get_roster_by_jid/3, + roster_subscribe/4, get_roster_by_jid_with_groups/3, + remove_user/2, update_roster/4, del_roster/3, transaction/2, + read_subscription_and_groups/3, get_only_items/2, import/2]). + +-include("jlib.hrl"). +-include("mod_roster.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +read_roster_version(LUser, LServer) -> + case ejabberd_riak:get(roster_version, roster_version_schema(), + {LUser, LServer}) of + {ok, #roster_version{version = V}} -> V; + _Err -> error + end. + +write_roster_version(LUser, LServer, _InTransaction, Ver) -> + US = {LUser, LServer}, + ejabberd_riak:put(#roster_version{us = US, version = Ver}, + roster_version_schema()). + +get_roster(LUser, LServer) -> + case ejabberd_riak:get_by_index(roster, roster_schema(), + <<"us">>, {LUser, LServer}) of + {ok, Items} -> Items; + _Err -> [] + end. + +get_roster_by_jid(LUser, LServer, LJID) -> + case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of + {ok, I} -> + I#roster{jid = LJID, name = <<"">>, groups = [], xs = []}; + {error, notfound} -> + #roster{usj = {LUser, LServer, LJID}, + us = {LUser, LServer}, jid = LJID}; + Err -> + exit(Err) + end. + +get_only_items(LUser, LServer) -> + get_roster(LUser, LServer). + +roster_subscribe(LUser, LServer, _LJID, Item) -> + ejabberd_riak:put(Item, roster_schema(), + [{'2i', [{<<"us">>, {LUser, LServer}}]}]). + +transaction(_LServer, F) -> + {atomic, F()}. + +get_roster_by_jid_with_groups(LUser, LServer, LJID) -> + case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of + {ok, I} -> + I; + {error, notfound} -> + #roster{usj = {LUser, LServer, LJID}, + us = {LUser, LServer}, jid = LJID}; + Err -> + exit(Err) + end. + +remove_user(LUser, LServer) -> + {atomic, ejabberd_riak:delete_by_index(roster, <<"us">>, {LUser, LServer})}. + +update_roster(LUser, LServer, _LJID, Item) -> + ejabberd_riak:put(Item, roster_schema(), + [{'2i', [{<<"us">>, {LUser, LServer}}]}]). + +del_roster(LUser, LServer, LJID) -> + ejabberd_riak:delete(roster, {LUser, LServer, LJID}). + +read_subscription_and_groups(LUser, LServer, LJID) -> + case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of + {ok, #roster{subscription = Subscription, + groups = Groups}} -> + {Subscription, Groups}; + _ -> + error + end. + +import(_LServer, #roster{us = {LUser, LServer}} = R) -> + ejabberd_riak:put(R, roster_schema(), + [{'2i', [{<<"us">>, {LUser, LServer}}]}]); +import(_LServer, #roster_version{} = RV) -> + ejabberd_riak:put(RV, roster_version_schema()). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +roster_schema() -> + {record_info(fields, roster), #roster{}}. + +roster_version_schema() -> + {record_info(fields, roster_version), #roster_version{}}. diff --git a/src/mod_roster_sql.erl b/src/mod_roster_sql.erl new file mode 100644 index 000000000..826628659 --- /dev/null +++ b/src/mod_roster_sql.erl @@ -0,0 +1,308 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_roster_sql). + +-behaviour(mod_roster). + +%% API +-export([init/2, read_roster_version/2, write_roster_version/4, + get_roster/2, get_roster_by_jid/3, + roster_subscribe/4, get_roster_by_jid_with_groups/3, + remove_user/2, update_roster/4, del_roster/3, transaction/2, + read_subscription_and_groups/3, get_only_items/2, + import/1, import/2, export/1]). + +-include("jlib.hrl"). +-include("mod_roster.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +read_roster_version(LUser, LServer) -> + case odbc_queries:get_roster_version(LServer, LUser) of + {selected, [{Version}]} -> Version; + {selected, []} -> error + end. + +write_roster_version(LUser, LServer, InTransaction, Ver) -> + Username = ejabberd_odbc:escape(LUser), + EVer = ejabberd_odbc:escape(Ver), + if InTransaction -> + odbc_queries:set_roster_version(Username, EVer); + true -> + odbc_queries:sql_transaction( + LServer, + fun () -> + odbc_queries:set_roster_version(Username, EVer) + end) + end. + +get_roster(LUser, LServer) -> + case catch odbc_queries:get_roster(LServer, LUser) of + {selected, Items} when is_list(Items) -> + JIDGroups = case catch odbc_queries:get_roster_jid_groups( + LServer, LUser) of + {selected, JGrps} when is_list(JGrps) -> + JGrps; + _ -> + [] + end, + GroupsDict = lists:foldl(fun({J, G}, Acc) -> + dict:append(J, G, Acc) + end, + dict:new(), JIDGroups), + lists:flatmap( + fun(I) -> + case raw_to_record(LServer, I) of + %% Bad JID in database: + error -> []; + R -> + SJID = jid:to_string(R#roster.jid), + Groups = case dict:find(SJID, GroupsDict) of + {ok, Gs} -> Gs; + error -> [] + end, + [R#roster{groups = Groups}] + end + end, Items); + _ -> + [] + end. + +get_roster_by_jid(LUser, LServer, LJID) -> + {selected, Res} = + odbc_queries:get_roster_by_jid(LServer, LUser, jid:to_string(LJID)), + case Res of + [] -> + #roster{usj = {LUser, LServer, LJID}, + us = {LUser, LServer}, jid = LJID}; + [I] -> + R = raw_to_record(LServer, I), + case R of + %% Bad JID in database: + error -> + #roster{usj = {LUser, LServer, LJID}, + us = {LUser, LServer}, jid = LJID}; + _ -> + R#roster{usj = {LUser, LServer, LJID}, + us = {LUser, LServer}, jid = LJID, name = <<"">>} + end + end. + +get_only_items(LUser, LServer) -> + case catch odbc_queries:get_roster(LServer, LUser) of + {selected, Is} when is_list(Is) -> + lists:map(fun(I) -> raw_to_record(LServer, I) end, Is); + _ -> [] + end. + +roster_subscribe(_LUser, _LServer, _LJID, Item) -> + ItemVals = record_to_row(Item), + odbc_queries:roster_subscribe(ItemVals). + +transaction(LServer, F) -> + ejabberd_odbc:sql_transaction(LServer, F). + +get_roster_by_jid_with_groups(LUser, LServer, LJID) -> + SJID = jid:to_string(LJID), + case odbc_queries:get_roster_by_jid(LServer, LUser, SJID) of + {selected, [I]} -> + R = raw_to_record(LServer, I), + Groups = + case odbc_queries:get_roster_groups(LServer, LUser, SJID) of + {selected, JGrps} when is_list(JGrps) -> + [JGrp || {JGrp} <- JGrps]; + _ -> [] + end, + R#roster{groups = Groups}; + {selected, []} -> + #roster{usj = {LUser, LServer, LJID}, + us = {LUser, LServer}, jid = LJID} + end. + +remove_user(LUser, LServer) -> + odbc_queries:del_user_roster_t(LServer, LUser), + {atomic, ok}. + +update_roster(LUser, LServer, LJID, Item) -> + SJID = jid:to_string(LJID), + ItemVals = record_to_row(Item), + ItemGroups = Item#roster.groups, + odbc_queries:update_roster(LServer, LUser, SJID, ItemVals, + ItemGroups). + +del_roster(LUser, LServer, LJID) -> + SJID = jid:to_string(LJID), + odbc_queries:del_roster(LServer, LUser, SJID). + +read_subscription_and_groups(LUser, LServer, LJID) -> + SJID = jid:to_string(LJID), + case catch odbc_queries:get_subscription(LServer, LUser, SJID) of + {selected, [{SSubscription}]} -> + Subscription = case SSubscription of + <<"B">> -> both; + <<"T">> -> to; + <<"F">> -> from; + _ -> none + end, + Groups = case catch odbc_queries:get_rostergroup_by_jid( + LServer, LUser, SJID) of + {selected, JGrps} when is_list(JGrps) -> + [JGrp || {JGrp} <- JGrps]; + _ -> [] + end, + {Subscription, Groups}; + _ -> + error + end. + +export(_Server) -> + [{roster, + fun(Host, #roster{usj = {LUser, LServer, LJID}} = R) + when LServer == Host -> + Username = ejabberd_odbc:escape(LUser), + SJID = ejabberd_odbc:escape(jid:to_string(LJID)), + ItemVals = record_to_string(R), + ItemGroups = groups_to_string(R), + odbc_queries:update_roster_sql(Username, SJID, + ItemVals, ItemGroups); + (_Host, _R) -> + [] + end}, + {roster_version, + fun(Host, #roster_version{us = {LUser, LServer}, version = Ver}) + when LServer == Host -> + Username = ejabberd_odbc:escape(LUser), + SVer = ejabberd_odbc:escape(Ver), + [[<<"delete from roster_version where username='">>, + Username, <<"';">>], + [<<"insert into roster_version(username, version) values('">>, + Username, <<"', '">>, SVer, <<"');">>]]; + (_Host, _R) -> + [] + end}]. + +import(LServer) -> + [{<<"select username, jid, nick, subscription, " + "ask, askmessage, server, subscribe, type from rosterusers;">>, + fun([LUser, JID|_] = Row) -> + Item = raw_to_record(LServer, Row), + Username = ejabberd_odbc:escape(LUser), + SJID = ejabberd_odbc:escape(JID), + {selected, _, Rows} = + ejabberd_odbc:sql_query_t( + [<<"select grp from rostergroups where username='">>, + Username, <<"' and jid='">>, SJID, <<"'">>]), + Groups = [Grp || [Grp] <- Rows], + Item#roster{groups = Groups} + end}, + {<<"select username, version from roster_version;">>, + fun([LUser, Ver]) -> + #roster_version{us = {LUser, LServer}, version = Ver} + end}]. + +import(_, _) -> + pass. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +raw_to_record(LServer, + [User, SJID, Nick, SSubscription, SAsk, SAskMessage, + _SServer, _SSubscribe, _SType]) -> + raw_to_record(LServer, + {User, SJID, Nick, SSubscription, SAsk, SAskMessage, + _SServer, _SSubscribe, _SType}); +raw_to_record(LServer, + {User, SJID, Nick, SSubscription, SAsk, SAskMessage, + _SServer, _SSubscribe, _SType}) -> + case jid:from_string(SJID) of + error -> error; + JID -> + LJID = jid:tolower(JID), + Subscription = case SSubscription of + <<"B">> -> both; + <<"T">> -> to; + <<"F">> -> from; + _ -> none + end, + Ask = case SAsk of + <<"S">> -> subscribe; + <<"U">> -> unsubscribe; + <<"B">> -> both; + <<"O">> -> out; + <<"I">> -> in; + _ -> none + end, + #roster{usj = {User, LServer, LJID}, + us = {User, LServer}, jid = LJID, name = Nick, + subscription = Subscription, ask = Ask, + askmessage = SAskMessage} + end. + +record_to_string(#roster{us = {User, _Server}, + jid = JID, name = Name, subscription = Subscription, + ask = Ask, askmessage = AskMessage}) -> + Username = ejabberd_odbc:escape(User), + SJID = + ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))), + Nick = ejabberd_odbc:escape(Name), + SSubscription = case Subscription of + both -> <<"B">>; + to -> <<"T">>; + from -> <<"F">>; + none -> <<"N">> + end, + SAsk = case Ask of + subscribe -> <<"S">>; + unsubscribe -> <<"U">>; + both -> <<"B">>; + out -> <<"O">>; + in -> <<"I">>; + none -> <<"N">> + end, + SAskMessage = ejabberd_odbc:escape(AskMessage), + [Username, SJID, Nick, SSubscription, SAsk, SAskMessage, + <<"N">>, <<"">>, <<"item">>]. + +record_to_row( + #roster{us = {LUser, _LServer}, + jid = JID, name = Name, subscription = Subscription, + ask = Ask, askmessage = AskMessage}) -> + SJID = jid:to_string(jid:tolower(JID)), + SSubscription = case Subscription of + both -> <<"B">>; + to -> <<"T">>; + from -> <<"F">>; + none -> <<"N">> + end, + SAsk = case Ask of + subscribe -> <<"S">>; + unsubscribe -> <<"U">>; + both -> <<"B">>; + out -> <<"O">>; + in -> <<"I">>; + none -> <<"N">> + end, + {LUser, SJID, Name, SSubscription, SAsk, AskMessage}. + +groups_to_string(#roster{us = {User, _Server}, + jid = JID, groups = Groups}) -> + Username = ejabberd_odbc:escape(User), + SJID = + ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))), + lists:foldl(fun (<<"">>, Acc) -> Acc; + (Group, Acc) -> + G = ejabberd_odbc:escape(Group), + [[Username, SJID, G] | Acc] + end, + [], Groups). From f8e3560ad262cdc1734b2ba565de2e0e0afe9f8b Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 14 Apr 2016 11:45:43 +0300 Subject: [PATCH 10/15] Clean mod_shared_roster.erl from DB specific code --- include/mod_shared_roster.hrl | 5 + src/mod_shared_roster.erl | 496 ++++--------------------------- src/mod_shared_roster_mnesia.erl | 167 +++++++++++ src/mod_shared_roster_riak.erl | 139 +++++++++ src/mod_shared_roster_sql.erl | 212 +++++++++++++ 5 files changed, 579 insertions(+), 440 deletions(-) create mode 100644 include/mod_shared_roster.hrl create mode 100644 src/mod_shared_roster_mnesia.erl create mode 100644 src/mod_shared_roster_riak.erl create mode 100644 src/mod_shared_roster_sql.erl diff --git a/include/mod_shared_roster.hrl b/include/mod_shared_roster.hrl new file mode 100644 index 000000000..1f96b3034 --- /dev/null +++ b/include/mod_shared_roster.hrl @@ -0,0 +1,5 @@ +-record(sr_group, {group_host = {<<"">>, <<"">>} :: {'$1' | binary(), '$2' | binary()}, + opts = [] :: list() | '_' | '$2'}). + +-record(sr_user, {us = {<<"">>, <<"">>} :: {binary(), binary()}, + group_host = {<<"">>, <<"">>} :: {binary(), binary()}}). diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl index 212a7d47f..6670cf77b 100644 --- a/src/mod_shared_roster.erl +++ b/src/mod_shared_roster.erl @@ -38,7 +38,7 @@ list_groups/1, create_group/2, create_group/3, delete_group/2, get_group_opts/2, set_group_opts/3, get_group_users/2, get_group_explicit_users/2, - is_user_in_group/3, add_user_to_group/3, + is_user_in_group/3, add_user_to_group/3, opts_to_binary/1, remove_user_from_group/3, mod_opt_type/1]). -include("ejabberd.hrl"). @@ -52,25 +52,28 @@ -include("ejabberd_web_admin.hrl"). --record(sr_group, {group_host = {<<"">>, <<"">>} :: {'$1' | binary(), '$2' | binary()}, - opts = [] :: list() | '_' | '$2'}). +-include("mod_shared_roster.hrl"). --record(sr_user, {us = {<<"">>, <<"">>} :: {binary(), binary()}, - group_host = {<<"">>, <<"">>} :: {binary(), binary()}}). +-type group_options() :: [{atom(), any()}]. +-callback init(binary(), gen_mod:opts()) -> any(). +-callback import(binary(), #sr_user{} | #sr_group{}) -> ok | pass. +-callback list_groups(binary()) -> [binary()]. +-callback groups_with_opts(binary()) -> [{binary(), group_options()}]. +-callback create_group(binary(), binary(), group_options()) -> {atomic, any()}. +-callback delete_group(binary(), binary()) -> {atomic, any()}. +-callback get_group_opts(binary(), binary()) -> group_options() | error. +-callback set_group_opts(binary(), binary(), group_options()) -> {atomic, any()}. +-callback get_user_groups({binary(), binary()}, binary()) -> [binary()]. +-callback get_group_explicit_users(binary(), binary()) -> [{binary(), binary()}]. +-callback get_user_displayed_groups(binary(), binary(), group_options()) -> + [{binary(), group_options()}]. +-callback is_user_in_group({binary(), binary()}, binary(), binary()) -> boolean(). +-callback add_user_to_group(binary(), {binary(), binary()}, binary()) -> {atomic, any()}. +-callback remove_user_from_group(binary(), {binary(), binary()}, binary()) -> {atomic, any()}. start(Host, Opts) -> - case gen_mod:db_type(Host, Opts) of - mnesia -> - mnesia:create_table(sr_group, - [{disc_copies, [node()]}, - {attributes, record_info(fields, sr_group)}]), - mnesia:create_table(sr_user, - [{disc_copies, [node()]}, {type, bag}, - {attributes, record_info(fields, sr_user)}]), - update_tables(), - mnesia:add_table_index(sr_user, group_host); - _ -> ok - end, + Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod:init(Host, Opts), ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE, webadmin_menu, 70), ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE, @@ -391,195 +394,36 @@ process_subscription(Direction, User, Server, JID, end. list_groups(Host) -> - list_groups(Host, gen_mod:db_type(Host, ?MODULE)). - -list_groups(Host, mnesia) -> - mnesia:dirty_select(sr_group, - [{#sr_group{group_host = {'$1', '$2'}, _ = '_'}, - [{'==', '$2', Host}], ['$1']}]); -list_groups(Host, riak) -> - case ejabberd_riak:get_keys_by_index(sr_group, <<"host">>, Host) of - {ok, Gs} -> - [G || {G, _} <- Gs]; - _ -> - [] - end; -list_groups(Host, odbc) -> - case ejabberd_odbc:sql_query(Host, - [<<"select name from sr_group;">>]) - of - {selected, [<<"name">>], Rs} -> [G || [G] <- Rs]; - _ -> [] - end. + Mod = gen_mod:db_mod(Host, ?MODULE), + Mod:list_groups(Host). groups_with_opts(Host) -> - groups_with_opts(Host, gen_mod:db_type(Host, ?MODULE)). - -groups_with_opts(Host, mnesia) -> - Gs = mnesia:dirty_select(sr_group, - [{#sr_group{group_host = {'$1', Host}, opts = '$2', - _ = '_'}, - [], [['$1', '$2']]}]), - lists:map(fun ([G, O]) -> {G, O} end, Gs); -groups_with_opts(Host, riak) -> - case ejabberd_riak:get_by_index(sr_group, sr_group_schema(), - <<"host">>, Host) of - {ok, Rs} -> - [{G, O} || #sr_group{group_host = {G, _}, opts = O} <- Rs]; - _ -> - [] - end; -groups_with_opts(Host, odbc) -> - case ejabberd_odbc:sql_query(Host, - [<<"select name, opts from sr_group;">>]) - of - {selected, [<<"name">>, <<"opts">>], Rs} -> - [{G, opts_to_binary(ejabberd_odbc:decode_term(Opts))} - || [G, Opts] <- Rs]; - _ -> [] - end. + Mod = gen_mod:db_mod(Host, ?MODULE), + Mod:groups_with_opts(Host). create_group(Host, Group) -> create_group(Host, Group, []). create_group(Host, Group, Opts) -> - create_group(Host, Group, Opts, - gen_mod:db_type(Host, ?MODULE)). - -create_group(Host, Group, Opts, mnesia) -> - R = #sr_group{group_host = {Group, Host}, opts = Opts}, - F = fun () -> mnesia:write(R) end, - mnesia:transaction(F); -create_group(Host, Group, Opts, riak) -> - {atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host}, - opts = Opts}, - sr_group_schema(), - [{'2i', [{<<"host">>, Host}]}])}; -create_group(Host, Group, Opts, odbc) -> - SGroup = ejabberd_odbc:escape(Group), - SOpts = ejabberd_odbc:encode_term(Opts), - F = fun () -> - odbc_queries:update_t(<<"sr_group">>, - [<<"name">>, <<"opts">>], [SGroup, SOpts], - [<<"name='">>, SGroup, <<"'">>]) - end, - ejabberd_odbc:sql_transaction(Host, F). + Mod = gen_mod:db_mod(Host, ?MODULE), + Mod:create_group(Host, Group, Opts). delete_group(Host, Group) -> - delete_group(Host, Group, - gen_mod:db_type(Host, ?MODULE)). - -delete_group(Host, Group, mnesia) -> - GroupHost = {Group, Host}, - F = fun () -> - mnesia:delete({sr_group, GroupHost}), - Users = mnesia:index_read(sr_user, GroupHost, - #sr_user.group_host), - lists:foreach(fun (UserEntry) -> - mnesia:delete_object(UserEntry) - end, - Users) - end, - mnesia:transaction(F); -delete_group(Host, Group, riak) -> - try - ok = ejabberd_riak:delete(sr_group, {Group, Host}), - ok = ejabberd_riak:delete_by_index(sr_user, <<"group_host">>, - {Group, Host}), - {atomic, ok} - catch _:{badmatch, Err} -> - {atomic, Err} - end; -delete_group(Host, Group, odbc) -> - SGroup = ejabberd_odbc:escape(Group), - F = fun () -> - ejabberd_odbc:sql_query_t([<<"delete from sr_group where name='">>, - SGroup, <<"';">>]), - ejabberd_odbc:sql_query_t([<<"delete from sr_user where grp='">>, - SGroup, <<"';">>]) - end, - case ejabberd_odbc:sql_transaction(Host, F) of - {atomic,{updated,_}} -> {atomic, ok}; - Res -> Res - end. + Mod = gen_mod:db_mod(Host, ?MODULE), + Mod:delete_group(Host, Group). get_group_opts(Host, Group) -> - get_group_opts(Host, Group, - gen_mod:db_type(Host, ?MODULE)). - -get_group_opts(Host, Group, mnesia) -> - case catch mnesia:dirty_read(sr_group, {Group, Host}) of - [#sr_group{opts = Opts}] -> Opts; - _ -> error - end; -get_group_opts(Host, Group, riak) -> - case ejabberd_riak:get(sr_group, sr_group_schema(), {Group, Host}) of - {ok, #sr_group{opts = Opts}} -> Opts; - _ -> error - end; -get_group_opts(Host, Group, odbc) -> - SGroup = ejabberd_odbc:escape(Group), - case catch ejabberd_odbc:sql_query(Host, - [<<"select opts from sr_group where name='">>, - SGroup, <<"';">>]) - of - {selected, [<<"opts">>], [[SOpts]]} -> - opts_to_binary(ejabberd_odbc:decode_term(SOpts)); - _ -> error - end. + Mod = gen_mod:db_mod(Host, ?MODULE), + Mod:get_group_opts(Host, Group). set_group_opts(Host, Group, Opts) -> - set_group_opts(Host, Group, Opts, - gen_mod:db_type(Host, ?MODULE)). - -set_group_opts(Host, Group, Opts, mnesia) -> - R = #sr_group{group_host = {Group, Host}, opts = Opts}, - F = fun () -> mnesia:write(R) end, - mnesia:transaction(F); -set_group_opts(Host, Group, Opts, riak) -> - {atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host}, - opts = Opts}, - sr_group_schema(), - [{'2i', [{<<"host">>, Host}]}])}; -set_group_opts(Host, Group, Opts, odbc) -> - SGroup = ejabberd_odbc:escape(Group), - SOpts = ejabberd_odbc:encode_term(Opts), - F = fun () -> - odbc_queries:update_t(<<"sr_group">>, - [<<"name">>, <<"opts">>], [SGroup, SOpts], - [<<"name='">>, SGroup, <<"'">>]) - end, - ejabberd_odbc:sql_transaction(Host, F). + Mod = gen_mod:db_mod(Host, ?MODULE), + Mod:set_group_opts(Host, Group, Opts). get_user_groups(US) -> Host = element(2, US), - DBType = gen_mod:db_type(Host, ?MODULE), - get_user_groups(US, Host, DBType) ++ - get_special_users_groups(Host). - -get_user_groups(US, Host, mnesia) -> - case catch mnesia:dirty_read(sr_user, US) of - Rs when is_list(Rs) -> - [Group - || #sr_user{group_host = {Group, H}} <- Rs, H == Host]; - _ -> [] - end; -get_user_groups(US, Host, riak) -> - case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of - {ok, Rs} -> - [Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host]; - _ -> - [] - end; -get_user_groups(US, Host, odbc) -> - SJID = make_jid_s(US), - case catch ejabberd_odbc:sql_query(Host, - [<<"select grp from sr_user where jid='">>, - SJID, <<"';">>]) - of - {selected, [<<"grp">>], Rs} -> [G || [G] <- Rs]; - _ -> [] - end. + Mod = gen_mod:db_mod(Host, ?MODULE), + Mod:get_user_groups(US, Host) ++ get_special_users_groups(Host). is_group_enabled(Host1, Group1) -> {Host, Group} = split_grouphost(Host1, Group1), @@ -630,39 +474,8 @@ get_group_users(Host, Group, GroupOpts) -> ++ get_group_explicit_users(Host, Group). get_group_explicit_users(Host, Group) -> - get_group_explicit_users(Host, Group, - gen_mod:db_type(Host, ?MODULE)). - -get_group_explicit_users(Host, Group, mnesia) -> - Read = (catch mnesia:dirty_index_read(sr_user, - {Group, Host}, #sr_user.group_host)), - case Read of - Rs when is_list(Rs) -> [R#sr_user.us || R <- Rs]; - _ -> [] - end; -get_group_explicit_users(Host, Group, riak) -> - case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), - <<"group_host">>, {Group, Host}) of - {ok, Rs} -> - [R#sr_user.us || R <- Rs]; - _ -> - [] - end; -get_group_explicit_users(Host, Group, odbc) -> - SGroup = ejabberd_odbc:escape(Group), - case catch ejabberd_odbc:sql_query(Host, - [<<"select jid from sr_user where grp='">>, - SGroup, <<"';">>]) - of - {selected, [<<"jid">>], Rs} -> - lists:map(fun ([JID]) -> - {U, S, _} = - jid:tolower(jid:from_string(JID)), - {U, S} - end, - Rs); - _ -> [] - end. + Mod = gen_mod:db_mod(Host, ?MODULE), + Mod:get_group_explicit_users(Host, Group). get_group_name(Host1, Group1) -> {Host, Group} = split_grouphost(Host1, Group1), @@ -718,44 +531,10 @@ get_special_displayed_groups(GroupsOpts) -> %% for the list of groups of that server that user is member %% get the list of groups displayed get_user_displayed_groups(LUser, LServer, GroupsOpts) -> - Groups = get_user_displayed_groups(LUser, LServer, - GroupsOpts, - gen_mod:db_type(LServer, ?MODULE)), + Mod = gen_mod:db_mod(LServer, ?MODULE), + Groups = Mod:get_user_displayed_groups(LUser, LServer, GroupsOpts), displayed_groups(GroupsOpts, Groups). -get_user_displayed_groups(LUser, LServer, GroupsOpts, - mnesia) -> - case catch mnesia:dirty_read(sr_user, {LUser, LServer}) - of - Rs when is_list(Rs) -> - [{Group, proplists:get_value(Group, GroupsOpts, [])} - || #sr_user{group_host = {Group, H}} <- Rs, - H == LServer]; - _ -> [] - end; -get_user_displayed_groups(LUser, LServer, GroupsOpts, - riak) -> - case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), - <<"us">>, {LUser, LServer}) of - {ok, Rs} -> - [{Group, proplists:get_value(Group, GroupsOpts, [])} - || #sr_user{group_host = {Group, _}} <- Rs]; - _ -> - [] - end; -get_user_displayed_groups(LUser, LServer, GroupsOpts, - odbc) -> - SJID = make_jid_s(LUser, LServer), - case catch ejabberd_odbc:sql_query(LServer, - [<<"select grp from sr_user where jid='">>, - SJID, <<"';">>]) - of - {selected, [<<"grp">>], Rs} -> - [{Group, proplists:get_value(Group, GroupsOpts, [])} - || [Group] <- Rs]; - _ -> [] - end. - %% @doc Get the list of groups that are displayed to this user get_user_displayed_groups(US) -> Host = element(2, US), @@ -779,42 +558,12 @@ get_user_displayed_groups(US) -> is_group_enabled(Host, Group)]. is_user_in_group(US, Group, Host) -> - is_user_in_group(US, Group, Host, - gen_mod:db_type(Host, ?MODULE)). - -is_user_in_group(US, Group, Host, mnesia) -> - case catch mnesia:dirty_match_object(#sr_user{us = US, - group_host = {Group, Host}}) - of - [] -> lists:member(US, get_group_users(Host, Group)); - _ -> true - end; -is_user_in_group(US, Group, Host, riak) -> - case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of - {ok, Rs} -> - case lists:any( - fun(#sr_user{group_host = {G, H}}) -> - (Group == G) and (Host == H) - end, Rs) of - false -> - lists:member(US, get_group_users(Host, Group)); - true -> - true - end; - _Err -> - false - end; -is_user_in_group(US, Group, Host, odbc) -> - SJID = make_jid_s(US), - SGroup = ejabberd_odbc:escape(Group), - case catch ejabberd_odbc:sql_query(Host, - [<<"select * from sr_user where jid='">>, - SJID, <<"' and grp='">>, SGroup, - <<"';">>]) - of - {selected, _, []} -> - lists:member(US, get_group_users(Host, Group)); - _ -> true + Mod = gen_mod:db_mod(Host, ?MODULE), + case Mod:is_user_in_group(US, Group, Host) of + false -> + lists:member(US, get_group_users(Host, Group)); + true -> + true end. %% @spec (Host::string(), {User::string(), Server::string()}, Group::string()) -> {atomic, ok} @@ -837,31 +586,10 @@ add_user_to_group(Host, US, Group) -> push_displayed_to_user(LUser, LServer, Host, both, DisplayedGroups), broadcast_user_to_displayed(LUser, LServer, Host, both, DisplayedToGroups), broadcast_displayed_to_user(LUser, LServer, Host, both, DisplayedGroups), - add_user_to_group(Host, US, Group, gen_mod:db_type(Host, ?MODULE)) + Mod = gen_mod:db_mod(Host, ?MODULE), + Mod:add_user_to_group(Host, US, Group) end. -add_user_to_group(Host, US, Group, mnesia) -> - R = #sr_user{us = US, group_host = {Group, Host}}, - F = fun () -> mnesia:write(R) end, - mnesia:transaction(F); -add_user_to_group(Host, US, Group, riak) -> - {atomic, ejabberd_riak:put( - #sr_user{us = US, group_host = {Group, Host}}, - sr_user_schema(), - [{i, {US, {Group, Host}}}, - {'2i', [{<<"us">>, US}, - {<<"group_host">>, {Group, Host}}]}])}; -add_user_to_group(Host, US, Group, odbc) -> - SJID = make_jid_s(US), - SGroup = ejabberd_odbc:escape(Group), - F = fun () -> - odbc_queries:update_t(<<"sr_user">>, - [<<"jid">>, <<"grp">>], [SJID, SGroup], - [<<"jid='">>, SJID, <<"' and grp='">>, - SGroup, <<"'">>]) - end, - ejabberd_odbc:sql_transaction(Host, F). - get_displayed_groups(Group, LServer) -> GroupsOpts = groups_with_opts(LServer), GroupOpts = proplists:get_value(Group, GroupsOpts, []), @@ -894,8 +622,8 @@ remove_user_from_group(Host, US, Group) -> end, (?MODULE):set_group_opts(Host, Group, NewGroupOpts); nomatch -> - Result = remove_user_from_group(Host, US, Group, - gen_mod:db_type(Host, ?MODULE)), + Mod = gen_mod:db_mod(Host, ?MODULE), + Result = Mod:remove_user_from_group(Host, US, Group), DisplayedToGroups = displayed_to_groups(Group, Host), DisplayedGroups = get_displayed_groups(Group, LServer), push_user_to_displayed(LUser, LServer, Group, Host, remove, DisplayedToGroups), @@ -903,23 +631,6 @@ remove_user_from_group(Host, US, Group) -> Result end. -remove_user_from_group(Host, US, Group, mnesia) -> - R = #sr_user{us = US, group_host = {Group, Host}}, - F = fun () -> mnesia:delete_object(R) end, - mnesia:transaction(F); -remove_user_from_group(Host, US, Group, riak) -> - {atomic, ejabberd_riak:delete(sr_group, {US, {Group, Host}})}; -remove_user_from_group(Host, US, Group, odbc) -> - SJID = make_jid_s(US), - SGroup = ejabberd_odbc:escape(Group), - F = fun () -> - ejabberd_odbc:sql_query_t([<<"delete from sr_user where jid='">>, - SJID, <<"' and grp='">>, SGroup, - <<"';">>]), - ok - end, - ejabberd_odbc:sql_transaction(Host, F). - push_members_to_user(LUser, LServer, Group, Host, Subscription) -> GroupsOpts = groups_with_opts(LServer), @@ -1385,13 +1096,6 @@ displayed_groups_update(Members, DisplayedGroups, Subscription) -> end end, Members). -make_jid_s(U, S) -> - ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:make(U, - S, - <<"">>)))). - -make_jid_s({U, S}) -> make_jid_s(U, S). - opts_to_binary(Opts) -> lists:map( fun({name, Name}) -> @@ -1404,105 +1108,17 @@ opts_to_binary(Opts) -> Opt end, Opts). -sr_group_schema() -> - {record_info(fields, sr_group), #sr_group{}}. - -sr_user_schema() -> - {record_info(fields, sr_user), #sr_user{}}. - -update_tables() -> - update_sr_group_table(), - update_sr_user_table(). - -update_sr_group_table() -> - Fields = record_info(fields, sr_group), - case mnesia:table_info(sr_group, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - sr_group, Fields, set, - fun(#sr_group{group_host = {G, _}}) -> G end, - fun(#sr_group{group_host = {G, H}, - opts = Opts} = R) -> - R#sr_group{group_host = {iolist_to_binary(G), - iolist_to_binary(H)}, - opts = opts_to_binary(Opts)} - end); - _ -> - ?INFO_MSG("Recreating sr_group table", []), - mnesia:transform_table(sr_group, ignore, Fields) - end. - -update_sr_user_table() -> - Fields = record_info(fields, sr_user), - case mnesia:table_info(sr_user, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - sr_user, Fields, bag, - fun(#sr_user{us = {U, _}}) -> U end, - fun(#sr_user{us = {U, S}, group_host = {G, H}} = R) -> - R#sr_user{us = {iolist_to_binary(U), iolist_to_binary(S)}, - group_host = {iolist_to_binary(G), - iolist_to_binary(H)}} - end); - _ -> - ?INFO_MSG("Recreating sr_user table", []), - mnesia:transform_table(sr_user, ignore, Fields) - end. - -export(_Server) -> - [{sr_group, - fun(Host, #sr_group{group_host = {Group, LServer}, opts = Opts}) - when LServer == Host -> - SGroup = ejabberd_odbc:escape(Group), - SOpts = ejabberd_odbc:encode_term(Opts), - [[<<"delete from sr_group where name='">>, Group, <<"';">>], - [<<"insert into sr_group(name, opts) values ('">>, - SGroup, <<"', '">>, SOpts, <<"');">>]]; - (_Host, _R) -> - [] - end}, - {sr_user, - fun(Host, #sr_user{us = {U, S}, group_host = {Group, LServer}}) - when LServer == Host -> - SGroup = ejabberd_odbc:escape(Group), - SJID = ejabberd_odbc:escape( - jid:to_string( - jid:tolower( - jid:make(U, S, <<"">>)))), - [[<<"delete from sr_user where jid='">>, SJID, - <<"'and grp='">>, Group, <<"';">>], - [<<"insert into sr_user(jid, grp) values ('">>, - SJID, <<"', '">>, SGroup, <<"');">>]]; - (_Host, _R) -> - [] - end}]. +export(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:export(LServer). import(LServer) -> - [{<<"select name, opts from sr_group;">>, - fun([Group, SOpts]) -> - #sr_group{group_host = {Group, LServer}, - opts = ejabberd_odbc:decode_term(SOpts)} - end}, - {<<"select jid, grp from sr_user;">>, - fun([SJID, Group]) -> - #jid{luser = U, lserver = S} = jid:from_string(SJID), - #sr_user{us = {U, S}, group_host = {Group, LServer}} - end}]. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:import(LServer). -import(_LServer, mnesia, #sr_group{} = G) -> - mnesia:dirty_write(G); - -import(_LServer, mnesia, #sr_user{} = U) -> - mnesia:dirty_write(U); -import(_LServer, riak, #sr_group{group_host = {_, Host}} = G) -> - ejabberd_riak:put(G, sr_group_schema(), [{'2i', [{<<"host">>, Host}]}]); -import(_LServer, riak, #sr_user{us = US, group_host = {Group, Host}} = User) -> - ejabberd_riak:put(User, sr_user_schema(), - [{i, {US, {Group, Host}}}, - {'2i', [{<<"us">>, US}, - {<<"group_host">>, {Group, Host}}]}]); -import(_, _, _) -> - pass. +import(LServer, DBType, Data) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, Data). mod_opt_type(db_type) -> fun gen_mod:v_db/1; mod_opt_type(_) -> [db_type]. diff --git a/src/mod_shared_roster_mnesia.erl b/src/mod_shared_roster_mnesia.erl new file mode 100644 index 000000000..ca2e55e2f --- /dev/null +++ b/src/mod_shared_roster_mnesia.erl @@ -0,0 +1,167 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_shared_roster_mnesia). + +-behaviour(mod_shared_roster). + +%% API +-export([init/2, list_groups/1, groups_with_opts/1, create_group/3, + delete_group/2, get_group_opts/2, set_group_opts/3, + get_user_groups/2, get_group_explicit_users/2, + get_user_displayed_groups/3, is_user_in_group/3, + add_user_to_group/3, remove_user_from_group/3, import/2]). + +-include("jlib.hrl"). +-include("mod_roster.hrl"). +-include("mod_shared_roster.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + mnesia:create_table(sr_group, + [{disc_copies, [node()]}, + {attributes, record_info(fields, sr_group)}]), + mnesia:create_table(sr_user, + [{disc_copies, [node()]}, {type, bag}, + {attributes, record_info(fields, sr_user)}]), + update_tables(), + mnesia:add_table_index(sr_user, group_host). + +list_groups(Host) -> + mnesia:dirty_select(sr_group, + [{#sr_group{group_host = {'$1', '$2'}, _ = '_'}, + [{'==', '$2', Host}], ['$1']}]). + +groups_with_opts(Host) -> + Gs = mnesia:dirty_select(sr_group, + [{#sr_group{group_host = {'$1', Host}, opts = '$2', + _ = '_'}, + [], [['$1', '$2']]}]), + lists:map(fun ([G, O]) -> {G, O} end, Gs). + +create_group(Host, Group, Opts) -> + R = #sr_group{group_host = {Group, Host}, opts = Opts}, + F = fun () -> mnesia:write(R) end, + mnesia:transaction(F). + +delete_group(Host, Group) -> + GroupHost = {Group, Host}, + F = fun () -> + mnesia:delete({sr_group, GroupHost}), + Users = mnesia:index_read(sr_user, GroupHost, + #sr_user.group_host), + lists:foreach(fun (UserEntry) -> + mnesia:delete_object(UserEntry) + end, + Users) + end, + mnesia:transaction(F). + +get_group_opts(Host, Group) -> + case catch mnesia:dirty_read(sr_group, {Group, Host}) of + [#sr_group{opts = Opts}] -> Opts; + _ -> error + end. + +set_group_opts(Host, Group, Opts) -> + R = #sr_group{group_host = {Group, Host}, opts = Opts}, + F = fun () -> mnesia:write(R) end, + mnesia:transaction(F). + +get_user_groups(US, Host) -> + case catch mnesia:dirty_read(sr_user, US) of + Rs when is_list(Rs) -> + [Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host]; + _ -> + [] + end. + +get_group_explicit_users(Host, Group) -> + Read = (catch mnesia:dirty_index_read(sr_user, + {Group, Host}, #sr_user.group_host)), + case Read of + Rs when is_list(Rs) -> [R#sr_user.us || R <- Rs]; + _ -> [] + end. + +get_user_displayed_groups(LUser, LServer, GroupsOpts) -> + case catch mnesia:dirty_read(sr_user, {LUser, LServer}) of + Rs when is_list(Rs) -> + [{Group, proplists:get_value(Group, GroupsOpts, [])} + || #sr_user{group_host = {Group, H}} <- Rs, + H == LServer]; + _ -> + [] + end. + +is_user_in_group(US, Group, Host) -> + case mnesia:dirty_match_object( + #sr_user{us = US, group_host = {Group, Host}}) of + [] -> false; + _ -> true + end. + +add_user_to_group(Host, US, Group) -> + R = #sr_user{us = US, group_host = {Group, Host}}, + F = fun () -> mnesia:write(R) end, + mnesia:transaction(F). + +remove_user_from_group(Host, US, Group) -> + R = #sr_user{us = US, group_host = {Group, Host}}, + F = fun () -> mnesia:delete_object(R) end, + mnesia:transaction(F). + +import(_LServer, #sr_group{} = G) -> + mnesia:dirty_write(G); +import(_LServer, #sr_user{} = U) -> + mnesia:dirty_write(U). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +update_tables() -> + update_sr_group_table(), + update_sr_user_table(). + +update_sr_group_table() -> + Fields = record_info(fields, sr_group), + case mnesia:table_info(sr_group, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + sr_group, Fields, set, + fun(#sr_group{group_host = {G, _}}) -> G end, + fun(#sr_group{group_host = {G, H}, + opts = Opts} = R) -> + R#sr_group{group_host = {iolist_to_binary(G), + iolist_to_binary(H)}, + opts = mod_shared_roster:opts_to_binary(Opts)} + end); + _ -> + ?INFO_MSG("Recreating sr_group table", []), + mnesia:transform_table(sr_group, ignore, Fields) + end. + +update_sr_user_table() -> + Fields = record_info(fields, sr_user), + case mnesia:table_info(sr_user, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + sr_user, Fields, bag, + fun(#sr_user{us = {U, _}}) -> U end, + fun(#sr_user{us = {U, S}, group_host = {G, H}} = R) -> + R#sr_user{us = {iolist_to_binary(U), iolist_to_binary(S)}, + group_host = {iolist_to_binary(G), + iolist_to_binary(H)}} + end); + _ -> + ?INFO_MSG("Recreating sr_user table", []), + mnesia:transform_table(sr_user, ignore, Fields) + end. diff --git a/src/mod_shared_roster_riak.erl b/src/mod_shared_roster_riak.erl new file mode 100644 index 000000000..0df35e37d --- /dev/null +++ b/src/mod_shared_roster_riak.erl @@ -0,0 +1,139 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_shared_roster_riak). + +-behaviour(mod_shared_roster). + +%% API +-export([init/2, list_groups/1, groups_with_opts/1, create_group/3, + delete_group/2, get_group_opts/2, set_group_opts/3, + get_user_groups/2, get_group_explicit_users/2, + get_user_displayed_groups/3, is_user_in_group/3, + add_user_to_group/3, remove_user_from_group/3, import/2]). + +-include("jlib.hrl"). +-include("mod_roster.hrl"). +-include("mod_shared_roster.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +list_groups(Host) -> + case ejabberd_riak:get_keys_by_index(sr_group, <<"host">>, Host) of + {ok, Gs} -> + [G || {G, _} <- Gs]; + _ -> + [] + end. + +groups_with_opts(Host) -> + case ejabberd_riak:get_by_index(sr_group, sr_group_schema(), + <<"host">>, Host) of + {ok, Rs} -> + [{G, O} || #sr_group{group_host = {G, _}, opts = O} <- Rs]; + _ -> + [] + end. + +create_group(Host, Group, Opts) -> + {atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host}, + opts = Opts}, + sr_group_schema(), + [{'2i', [{<<"host">>, Host}]}])}. + +delete_group(Host, Group) -> + try + ok = ejabberd_riak:delete(sr_group, {Group, Host}), + ok = ejabberd_riak:delete_by_index(sr_user, <<"group_host">>, + {Group, Host}), + {atomic, ok} + catch _:{badmatch, Err} -> + {atomic, Err} + end. + +get_group_opts(Host, Group) -> + case ejabberd_riak:get(sr_group, sr_group_schema(), {Group, Host}) of + {ok, #sr_group{opts = Opts}} -> Opts; + _ -> error + end. + +set_group_opts(Host, Group, Opts) -> + {atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host}, + opts = Opts}, + sr_group_schema(), + [{'2i', [{<<"host">>, Host}]}])}. + +get_user_groups(US, Host) -> + case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of + {ok, Rs} -> + [Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host]; + _ -> + [] + end. + +get_group_explicit_users(Host, Group) -> + case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), + <<"group_host">>, {Group, Host}) of + {ok, Rs} -> + [R#sr_user.us || R <- Rs]; + _ -> + [] + end. + +get_user_displayed_groups(LUser, LServer, GroupsOpts) -> + case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), + <<"us">>, {LUser, LServer}) of + {ok, Rs} -> + [{Group, proplists:get_value(Group, GroupsOpts, [])} + || #sr_user{group_host = {Group, _}} <- Rs]; + _ -> + [] + end. + +is_user_in_group(US, Group, Host) -> + case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of + {ok, Rs} -> + lists:any( + fun(#sr_user{group_host = {G, H}}) -> + (Group == G) and (Host == H) + end, Rs); + _Err -> + false + end. + +add_user_to_group(Host, US, Group) -> + {atomic, ejabberd_riak:put( + #sr_user{us = US, group_host = {Group, Host}}, + sr_user_schema(), + [{i, {US, {Group, Host}}}, + {'2i', [{<<"us">>, US}, + {<<"group_host">>, {Group, Host}}]}])}. + +remove_user_from_group(Host, US, Group) -> + {atomic, ejabberd_riak:delete(sr_group, {US, {Group, Host}})}. + +import(_LServer, #sr_group{group_host = {_, Host}} = G) -> + ejabberd_riak:put(G, sr_group_schema(), [{'2i', [{<<"host">>, Host}]}]); +import(_LServer, #sr_user{us = US, group_host = {Group, Host}} = User) -> + ejabberd_riak:put(User, sr_user_schema(), + [{i, {US, {Group, Host}}}, + {'2i', [{<<"us">>, US}, + {<<"group_host">>, {Group, Host}}]}]). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +sr_group_schema() -> + {record_info(fields, sr_group), #sr_group{}}. + +sr_user_schema() -> + {record_info(fields, sr_user), #sr_user{}}. diff --git a/src/mod_shared_roster_sql.erl b/src/mod_shared_roster_sql.erl new file mode 100644 index 000000000..21ea768af --- /dev/null +++ b/src/mod_shared_roster_sql.erl @@ -0,0 +1,212 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_shared_roster_sql). + +-behaviour(mod_shared_roster). + +%% API +-export([init/2, list_groups/1, groups_with_opts/1, create_group/3, + delete_group/2, get_group_opts/2, set_group_opts/3, + get_user_groups/2, get_group_explicit_users/2, + get_user_displayed_groups/3, is_user_in_group/3, + add_user_to_group/3, remove_user_from_group/3, import/1, + import/2, export/1]). + +-include("jlib.hrl"). +-include("mod_roster.hrl"). +-include("mod_shared_roster.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +list_groups(Host) -> + case ejabberd_odbc:sql_query( + Host, [<<"select name from sr_group;">>]) of + {selected, [<<"name">>], Rs} -> [G || [G] <- Rs]; + _ -> [] + end. + +groups_with_opts(Host) -> + case ejabberd_odbc:sql_query(Host, + [<<"select name, opts from sr_group;">>]) + of + {selected, [<<"name">>, <<"opts">>], Rs} -> + [{G, mod_shared_roster:opts_to_binary(ejabberd_odbc:decode_term(Opts))} + || [G, Opts] <- Rs]; + _ -> [] + end. + +create_group(Host, Group, Opts) -> + SGroup = ejabberd_odbc:escape(Group), + SOpts = ejabberd_odbc:encode_term(Opts), + F = fun () -> + odbc_queries:update_t(<<"sr_group">>, + [<<"name">>, <<"opts">>], [SGroup, SOpts], + [<<"name='">>, SGroup, <<"'">>]) + end, + ejabberd_odbc:sql_transaction(Host, F). + +delete_group(Host, Group) -> + SGroup = ejabberd_odbc:escape(Group), + F = fun () -> + ejabberd_odbc:sql_query_t([<<"delete from sr_group where name='">>, + SGroup, <<"';">>]), + ejabberd_odbc:sql_query_t([<<"delete from sr_user where grp='">>, + SGroup, <<"';">>]) + end, + case ejabberd_odbc:sql_transaction(Host, F) of + {atomic,{updated,_}} -> {atomic, ok}; + Res -> Res + end. + +get_group_opts(Host, Group) -> + SGroup = ejabberd_odbc:escape(Group), + case catch ejabberd_odbc:sql_query( + Host, + [<<"select opts from sr_group where name='">>, + SGroup, <<"';">>]) of + {selected, [<<"opts">>], [[SOpts]]} -> + mod_shared_roster:opts_to_binary(ejabberd_odbc:decode_term(SOpts)); + _ -> error + end. + +set_group_opts(Host, Group, Opts) -> + SGroup = ejabberd_odbc:escape(Group), + SOpts = ejabberd_odbc:encode_term(Opts), + F = fun () -> + odbc_queries:update_t(<<"sr_group">>, + [<<"name">>, <<"opts">>], [SGroup, SOpts], + [<<"name='">>, SGroup, <<"'">>]) + end, + ejabberd_odbc:sql_transaction(Host, F). + +get_user_groups(US, Host) -> + SJID = make_jid_s(US), + case catch ejabberd_odbc:sql_query( + Host, + [<<"select grp from sr_user where jid='">>, + SJID, <<"';">>]) of + {selected, [<<"grp">>], Rs} -> [G || [G] <- Rs]; + _ -> [] + end. + +get_group_explicit_users(Host, Group) -> + SGroup = ejabberd_odbc:escape(Group), + case catch ejabberd_odbc:sql_query( + Host, + [<<"select jid from sr_user where grp='">>, + SGroup, <<"';">>]) of + {selected, [<<"jid">>], Rs} -> + lists:map( + fun([JID]) -> + {U, S, _} = jid:tolower(jid:from_string(JID)), + {U, S} + end, Rs); + _ -> + [] + end. + +get_user_displayed_groups(LUser, LServer, GroupsOpts) -> + SJID = make_jid_s(LUser, LServer), + case catch ejabberd_odbc:sql_query( + LServer, + [<<"select grp from sr_user where jid='">>, + SJID, <<"';">>]) of + {selected, [<<"grp">>], Rs} -> + [{Group, proplists:get_value(Group, GroupsOpts, [])} + || [Group] <- Rs]; + _ -> [] + end. + +is_user_in_group(US, Group, Host) -> + SJID = make_jid_s(US), + SGroup = ejabberd_odbc:escape(Group), + case catch ejabberd_odbc:sql_query(Host, + [<<"select * from sr_user where jid='">>, + SJID, <<"' and grp='">>, SGroup, + <<"';">>]) of + {selected, _, []} -> false; + _ -> true + end. + +add_user_to_group(Host, US, Group) -> + SJID = make_jid_s(US), + SGroup = ejabberd_odbc:escape(Group), + F = fun () -> + odbc_queries:update_t(<<"sr_user">>, + [<<"jid">>, <<"grp">>], [SJID, SGroup], + [<<"jid='">>, SJID, <<"' and grp='">>, + SGroup, <<"'">>]) + end, + ejabberd_odbc:sql_transaction(Host, F). + +remove_user_from_group(Host, US, Group) -> + SJID = make_jid_s(US), + SGroup = ejabberd_odbc:escape(Group), + F = fun () -> + ejabberd_odbc:sql_query_t([<<"delete from sr_user where jid='">>, + SJID, <<"' and grp='">>, SGroup, + <<"';">>]), + ok + end, + ejabberd_odbc:sql_transaction(Host, F). + +export(_Server) -> + [{sr_group, + fun(Host, #sr_group{group_host = {Group, LServer}, opts = Opts}) + when LServer == Host -> + SGroup = ejabberd_odbc:escape(Group), + SOpts = ejabberd_odbc:encode_term(Opts), + [[<<"delete from sr_group where name='">>, Group, <<"';">>], + [<<"insert into sr_group(name, opts) values ('">>, + SGroup, <<"', '">>, SOpts, <<"');">>]]; + (_Host, _R) -> + [] + end}, + {sr_user, + fun(Host, #sr_user{us = {U, S}, group_host = {Group, LServer}}) + when LServer == Host -> + SGroup = ejabberd_odbc:escape(Group), + SJID = ejabberd_odbc:escape( + jid:to_string( + jid:tolower( + jid:make(U, S, <<"">>)))), + [[<<"delete from sr_user where jid='">>, SJID, + <<"'and grp='">>, Group, <<"';">>], + [<<"insert into sr_user(jid, grp) values ('">>, + SJID, <<"', '">>, SGroup, <<"');">>]]; + (_Host, _R) -> + [] + end}]. + +import(LServer) -> + [{<<"select name, opts from sr_group;">>, + fun([Group, SOpts]) -> + #sr_group{group_host = {Group, LServer}, + opts = ejabberd_odbc:decode_term(SOpts)} + end}, + {<<"select jid, grp from sr_user;">>, + fun([SJID, Group]) -> + #jid{luser = U, lserver = S} = jid:from_string(SJID), + #sr_user{us = {U, S}, group_host = {Group, LServer}} + end}]. + +import(_, _) -> + pass. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +make_jid_s(U, S) -> + ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:make(U, S, <<"">>)))). + +make_jid_s({U, S}) -> make_jid_s(U, S). From 79d64e0d71ffa8124186f6027d5695b93b8333b7 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 14 Apr 2016 12:18:04 +0300 Subject: [PATCH 11/15] Clean mod_irc.erl from DB specific code --- include/mod_irc.hrl | 15 ++++ src/mod_irc.erl | 176 ++++++----------------------------------- src/mod_irc_mnesia.erl | 69 ++++++++++++++++ src/mod_irc_riak.erl | 49 ++++++++++++ src/mod_irc_sql.erl | 91 +++++++++++++++++++++ 5 files changed, 250 insertions(+), 150 deletions(-) create mode 100644 include/mod_irc.hrl create mode 100644 src/mod_irc_mnesia.erl create mode 100644 src/mod_irc_riak.erl create mode 100644 src/mod_irc_sql.erl diff --git a/include/mod_irc.hrl b/include/mod_irc.hrl new file mode 100644 index 000000000..b9696a88b --- /dev/null +++ b/include/mod_irc.hrl @@ -0,0 +1,15 @@ +-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} | + {binary(), binary(), inet:port_number()} | + {binary(), binary()} | + {binary()}. + +-type irc_data() :: [{username, binary()} | {connections_params, [conn_param()]}]. + +-record(irc_connection, + {jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()}, + pid = self() :: pid()}). + +-record(irc_custom, + {us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, + binary()}, + data = [] :: irc_data()}). diff --git a/src/mod_irc.erl b/src/mod_irc.erl index f6e452d82..e0c658dea 100644 --- a/src/mod_irc.erl +++ b/src/mod_irc.erl @@ -33,7 +33,8 @@ %% API -export([start_link/2, start/2, stop/1, export/1, import/1, - import/3, closed_connection/3, get_connection_params/3]). + import/3, closed_connection/3, get_connection_params/3, + data_to_binary/2]). -export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, @@ -46,6 +47,8 @@ -include("adhoc.hrl"). +-include("mod_irc.hrl"). + -define(DEFAULT_IRC_ENCODING, <<"iso8859-15">>). -define(DEFAULT_IRC_PORT, 6667). @@ -58,27 +61,19 @@ [<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>, <<"utf-8">>, <<"utf-8+latin-1">>]). --type conn_param() :: {binary(), binary(), inet:port_number(), binary()} | - {binary(), binary(), inet:port_number()} | - {binary(), binary()} | - {binary()}. - --record(irc_connection, - {jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()}, - pid = self() :: pid()}). - --record(irc_custom, - {us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()}, - binary()}, - data = [] :: [{username, binary()} | - {connections_params, [conn_param()]}]}). - -record(state, {host = <<"">> :: binary(), server_host = <<"">> :: binary(), access = all :: atom()}). -define(PROCNAME, ejabberd_mod_irc). +-callback init(binary(), gen_mod:opts()) -> any(). +-callback import(binary(), #irc_custom{}) -> ok | pass. +-callback get_data(binary(), binary(), {binary(), binary()}) -> + error | empty | irc_data(). +-callback set_data(binary(), binary(), {binary(), binary()}, irc_data()) -> + {atomic, any()}. + %%==================================================================== %% API %%==================================================================== @@ -119,14 +114,8 @@ init([Host, Opts]) -> ejabberd:start_app(iconv), MyHost = gen_mod:get_opt_host(Host, Opts, <<"irc.@HOST@">>), - case gen_mod:db_type(Host, Opts) of - mnesia -> - mnesia:create_table(irc_custom, - [{disc_copies, [node()]}, - {attributes, record_info(fields, irc_custom)}]), - update_table(); - _ -> ok - end, + Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod:init(Host, Opts), Access = gen_mod:get_opt(access, Opts, fun(A) when is_atom(A) -> A end, all), @@ -597,43 +586,8 @@ process_irc_register(ServerHost, Host, From, _To, get_data(ServerHost, Host, From) -> LServer = jid:nameprep(ServerHost), - get_data(LServer, Host, From, - gen_mod:db_type(LServer, ?MODULE)). - -get_data(_LServer, Host, From, mnesia) -> - #jid{luser = LUser, lserver = LServer} = From, - US = {LUser, LServer}, - case catch mnesia:dirty_read({irc_custom, {US, Host}}) - of - {'EXIT', _Reason} -> error; - [] -> empty; - [#irc_custom{data = Data}] -> Data - end; -get_data(LServer, Host, From, riak) -> - #jid{luser = LUser, lserver = LServer} = From, - US = {LUser, LServer}, - case ejabberd_riak:get(irc_custom, irc_custom_schema(), {US, Host}) of - {ok, #irc_custom{data = Data}} -> - Data; - {error, notfound} -> - empty; - _Err -> - error - end; -get_data(LServer, Host, From, odbc) -> - SJID = - ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))), - SHost = ejabberd_odbc:escape(Host), - case catch ejabberd_odbc:sql_query(LServer, - [<<"select data from irc_custom where jid='">>, - SJID, <<"' and host='">>, SHost, - <<"';">>]) - of - {selected, [<<"data">>], [[SData]]} -> - data_to_binary(From, ejabberd_odbc:decode_term(SData)); - {'EXIT', _} -> error; - {selected, _, _} -> empty - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:get_data(LServer, Host, From). get_form(ServerHost, Host, From, [], Lang) -> #jid{user = User, server = Server} = From, @@ -743,37 +697,8 @@ get_form(_ServerHost, _Host, _, _, _Lang) -> set_data(ServerHost, Host, From, Data) -> LServer = jid:nameprep(ServerHost), - set_data(LServer, Host, From, data_to_binary(From, Data), - gen_mod:db_type(LServer, ?MODULE)). - -set_data(_LServer, Host, From, Data, mnesia) -> - {LUser, LServer, _} = jid:tolower(From), - US = {LUser, LServer}, - F = fun () -> - mnesia:write(#irc_custom{us_host = {US, Host}, - data = Data}) - end, - mnesia:transaction(F); -set_data(LServer, Host, From, Data, riak) -> - {LUser, LServer, _} = jid:tolower(From), - US = {LUser, LServer}, - {atomic, ejabberd_riak:put(#irc_custom{us_host = {US, Host}, - data = Data}, - irc_custom_schema())}; -set_data(LServer, Host, From, Data, odbc) -> - SJID = - ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))), - SHost = ejabberd_odbc:escape(Host), - SData = ejabberd_odbc:encode_term(Data), - F = fun () -> - odbc_queries:update_t(<<"irc_custom">>, - [<<"jid">>, <<"host">>, <<"data">>], - [SJID, SHost, SData], - [<<"jid='">>, SJID, <<"' and host='">>, - SHost, <<"'">>]), - ok - end, - ejabberd_odbc:sql_transaction(LServer, F). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:set_data(LServer, Host, From, data_to_binary(From, Data)). set_form(ServerHost, Host, From, [], Lang, XData) -> case {lists:keysearch(<<"username">>, 1, XData), @@ -1314,66 +1239,17 @@ conn_params_to_list(Params) -> Port, binary_to_list(P)} end, Params). -irc_custom_schema() -> - {record_info(fields, irc_custom), #irc_custom{}}. +export(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:export(LServer). -update_table() -> - Fields = record_info(fields, irc_custom), - case mnesia:table_info(irc_custom, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - irc_custom, Fields, set, - fun(#irc_custom{us_host = {_, H}}) -> H end, - fun(#irc_custom{us_host = {{U, S}, H}, - data = Data} = R) -> - JID = jid:make(U, S, <<"">>), - R#irc_custom{us_host = {{iolist_to_binary(U), - iolist_to_binary(S)}, - iolist_to_binary(H)}, - data = data_to_binary(JID, Data)} - end); - _ -> - ?INFO_MSG("Recreating irc_custom table", []), - mnesia:transform_table(irc_custom, ignore, Fields) - end. +import(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:import(LServer). -export(_Server) -> - [{irc_custom, - fun(Host, #irc_custom{us_host = {{U, S}, IRCHost}, - data = Data}) -> - case str:suffix(Host, IRCHost) of - true -> - SJID = ejabberd_odbc:escape( - jid:to_string( - jid:make(U, S, <<"">>))), - SIRCHost = ejabberd_odbc:escape(IRCHost), - SData = ejabberd_odbc:encode_term(Data), - [[<<"delete from irc_custom where jid='">>, SJID, - <<"' and host='">>, SIRCHost, <<"';">>], - [<<"insert into irc_custom(jid, host, " - "data) values ('">>, - SJID, <<"', '">>, SIRCHost, <<"', '">>, SData, - <<"');">>]]; - false -> - [] - end - end}]. - -import(_LServer) -> - [{<<"select jid, host, data from irc_custom;">>, - fun([SJID, IRCHost, SData]) -> - #jid{luser = U, lserver = S} = jid:from_string(SJID), - Data = ejabberd_odbc:decode_term(SData), - #irc_custom{us_host = {{U, S}, IRCHost}, - data = Data} - end}]. - -import(_LServer, mnesia, #irc_custom{} = R) -> - mnesia:dirty_write(R); -import(_LServer, riak, #irc_custom{} = R) -> - ejabberd_riak:put(R, irc_custom_schema()); -import(_, _, _) -> - pass. +import(LServer, DBType, Data) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, Data). mod_opt_type(access) -> fun (A) when is_atom(A) -> A end; diff --git a/src/mod_irc_mnesia.erl b/src/mod_irc_mnesia.erl new file mode 100644 index 000000000..9f8117ad3 --- /dev/null +++ b/src/mod_irc_mnesia.erl @@ -0,0 +1,69 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_irc_mnesia). + +-behaviour(mod_irc). + +%% API +-export([init/2, get_data/3, set_data/4, import/2]). + +-include("jlib.hrl"). +-include("mod_irc.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + mnesia:create_table(irc_custom, + [{disc_copies, [node()]}, + {attributes, record_info(fields, irc_custom)}]), + update_table(). + +get_data(_LServer, Host, From) -> + {U, S, _} = jid:tolower(From), + case catch mnesia:dirty_read({irc_custom, {{U, S}, Host}}) of + {'EXIT', _Reason} -> error; + [] -> empty; + [#irc_custom{data = Data}] -> Data + end. + +set_data(_LServer, Host, From, Data) -> + {U, S, _} = jid:tolower(From), + F = fun () -> + mnesia:write(#irc_custom{us_host = {{U, S}, Host}, + data = Data}) + end, + mnesia:transaction(F). + +import(_LServer, #irc_custom{} = R) -> + mnesia:dirty_write(R). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +update_table() -> + Fields = record_info(fields, irc_custom), + case mnesia:table_info(irc_custom, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + irc_custom, Fields, set, + fun(#irc_custom{us_host = {_, H}}) -> H end, + fun(#irc_custom{us_host = {{U, S}, H}, + data = Data} = R) -> + JID = jid:make(U, S, <<"">>), + R#irc_custom{us_host = {{iolist_to_binary(U), + iolist_to_binary(S)}, + iolist_to_binary(H)}, + data = mod_irc:data_to_binary(JID, Data)} + end); + _ -> + ?INFO_MSG("Recreating irc_custom table", []), + mnesia:transform_table(irc_custom, ignore, Fields) + end. diff --git a/src/mod_irc_riak.erl b/src/mod_irc_riak.erl new file mode 100644 index 000000000..6ac7befdf --- /dev/null +++ b/src/mod_irc_riak.erl @@ -0,0 +1,49 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_irc_riak). + +-behaviour(mod_irc). + +%% API +-export([init/2, get_data/3, set_data/4, import/2]). + +-include("jlib.hrl"). +-include("mod_irc.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +get_data(_LServer, Host, From) -> + {U, S, _} = jid:tolower(From), + case ejabberd_riak:get(irc_custom, irc_custom_schema(), {{U, S}, Host}) of + {ok, #irc_custom{data = Data}} -> + Data; + {error, notfound} -> + empty; + _Err -> + error + end. + +set_data(_LServer, Host, From, Data) -> + {U, S, _} = jid:tolower(From), + {atomic, ejabberd_riak:put(#irc_custom{us_host = {{U, S}, Host}, + data = Data}, + irc_custom_schema())}. + +import(_LServer, #irc_custom{} = R) -> + ejabberd_riak:put(R, irc_custom_schema()). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +irc_custom_schema() -> + {record_info(fields, irc_custom), #irc_custom{}}. diff --git a/src/mod_irc_sql.erl b/src/mod_irc_sql.erl new file mode 100644 index 000000000..bf6dbb48e --- /dev/null +++ b/src/mod_irc_sql.erl @@ -0,0 +1,91 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_irc_sql). + +-behaviour(mod_irc). + +%% API +-export([init/2, get_data/3, set_data/4, import/1, import/2, export/1]). + +-include("jlib.hrl"). +-include("mod_irc.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +get_data(LServer, Host, From) -> + LJID = jid:tolower(jid:remove_resource(From)), + SJID = ejabberd_odbc:escape(jid:to_string(LJID)), + SHost = ejabberd_odbc:escape(Host), + case catch ejabberd_odbc:sql_query( + LServer, + [<<"select data from irc_custom where jid='">>, + SJID, <<"' and host='">>, SHost, + <<"';">>]) of + {selected, [<<"data">>], [[SData]]} -> + mod_irc:data_to_binary(From, ejabberd_odbc:decode_term(SData)); + {'EXIT', _} -> error; + {selected, _, _} -> empty + end. + +set_data(LServer, Host, From, Data) -> + LJID = jid:tolower(jid:remove_resource(From)), + SJID = ejabberd_odbc:escape(jid:to_string(LJID)), + SHost = ejabberd_odbc:escape(Host), + SData = ejabberd_odbc:encode_term(Data), + F = fun () -> + odbc_queries:update_t(<<"irc_custom">>, + [<<"jid">>, <<"host">>, <<"data">>], + [SJID, SHost, SData], + [<<"jid='">>, SJID, <<"' and host='">>, + SHost, <<"'">>]), + ok + end, + ejabberd_odbc:sql_transaction(LServer, F). + +export(_Server) -> + [{irc_custom, + fun(Host, #irc_custom{us_host = {{U, S}, IRCHost}, + data = Data}) -> + case str:suffix(Host, IRCHost) of + true -> + SJID = ejabberd_odbc:escape( + jid:to_string( + jid:make(U, S, <<"">>))), + SIRCHost = ejabberd_odbc:escape(IRCHost), + SData = ejabberd_odbc:encode_term(Data), + [[<<"delete from irc_custom where jid='">>, SJID, + <<"' and host='">>, SIRCHost, <<"';">>], + [<<"insert into irc_custom(jid, host, " + "data) values ('">>, + SJID, <<"', '">>, SIRCHost, <<"', '">>, SData, + <<"');">>]]; + false -> + [] + end + end}]. + +import(_LServer) -> + [{<<"select jid, host, data from irc_custom;">>, + fun([SJID, IRCHost, SData]) -> + #jid{luser = U, lserver = S} = jid:from_string(SJID), + Data = ejabberd_odbc:decode_term(SData), + #irc_custom{us_host = {{U, S}, IRCHost}, + data = Data} + end}]. + +import(_, _) -> + pass. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== From c8c4a41b666440836ac8a44130a8a000c0d75ef4 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 14 Apr 2016 14:16:32 +0300 Subject: [PATCH 12/15] Clean mod_privacy.erl from DB specific code --- src/mod_privacy.erl | 721 +++---------------------------------- src/mod_privacy_mnesia.erl | 198 ++++++++++ src/mod_privacy_riak.erl | 160 ++++++++ src/mod_privacy_sql.erl | 397 ++++++++++++++++++++ 4 files changed, 801 insertions(+), 675 deletions(-) create mode 100644 src/mod_privacy_mnesia.erl create mode 100644 src/mod_privacy_riak.erl create mode 100644 src/mod_privacy_sql.erl diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl index ea36fed25..413dcb52a 100644 --- a/src/mod_privacy.erl +++ b/src/mod_privacy.erl @@ -33,19 +33,10 @@ -export([start/2, stop/1, process_iq/3, export/1, import/1, process_iq_set/4, process_iq_get/5, get_user_list/3, - check_packet/6, remove_user/2, item_to_raw/1, - raw_to_item/1, is_list_needdb/1, updated_list/3, + check_packet/6, remove_user/2, + is_list_needdb/1, updated_list/3, item_to_xml/1, get_user_lists/2, import/3, - set_privacy_list/1]). - --export([sql_add_privacy_list/2, - sql_get_default_privacy_list/2, - sql_get_default_privacy_list_t/1, - sql_get_privacy_list_data/3, - sql_get_privacy_list_data_by_id_t/1, - sql_get_privacy_list_id_t/2, - sql_set_default_privacy_list/2, sql_set_privacy_list/2, - privacy_schema/0, mod_opt_type/1]). + set_privacy_list/1, mod_opt_type/1]). -include("ejabberd.hrl"). -include("logger.hrl"). @@ -54,20 +45,24 @@ -include("mod_privacy.hrl"). -privacy_schema() -> - {record_info(fields, privacy), #privacy{}}. +-callback init(binary(), gen_mod:opts()) -> any(). +-callback import(binary(), #privacy{}) -> ok | pass. +-callback process_lists_get(binary(), binary()) -> {none | binary(), [xmlel()]} | error. +-callback process_list_get(binary(), binary(), binary()) -> [listitem()] | error | not_found. +-callback process_default_set(binary(), binary(), {value, binary()} | false) -> {atomic, any()}. +-callback process_active_set(binary(), binary(), binary()) -> [listitem()] | error. +-callback remove_privacy_list(binary(), binary(), binary()) -> {atomic, any()}. +-callback set_privacy_list(#privacy{}) -> any(). +-callback set_privacy_list(binary(), binary(), binary(), [listitem()]) -> {atomic, any()}. +-callback get_user_list(binary(), binary()) -> {none | binary(), [listitem()]}. +-callback get_user_lists(binary(), binary()) -> {ok, #privacy{}} | error. +-callback remove_user(binary(), binary()) -> {atomic, any()}. start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, one_queue), - case gen_mod:db_type(Host, Opts) of - mnesia -> - mnesia:create_table(privacy, - [{disc_copies, [node()]}, - {attributes, record_info(fields, privacy)}]), - update_table(); - _ -> ok - end, + Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod:init(Host, Opts), mod_disco:register_feature(Host, ?NS_PRIVACY), ejabberd_hooks:add(privacy_iq_get, Host, ?MODULE, process_iq_get, 50), @@ -124,9 +119,8 @@ process_iq_get(_, From, _To, #iq{lang = Lang, sub_el = SubEl}, end. process_lists_get(LUser, LServer, Active, Lang) -> - case process_lists_get_db(LUser, LServer, Active, - gen_mod:db_type(LServer, ?MODULE)) - of + Mod = gen_mod:db_mod(LServer, ?MODULE), + case Mod:process_lists_get(LUser, LServer) of error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}; {_Default, []} -> {result, @@ -153,57 +147,9 @@ process_lists_get(LUser, LServer, Active, Lang) -> children = ADItems}]} end. -process_lists_get_db(LUser, LServer, _Active, mnesia) -> - case catch mnesia:dirty_read(privacy, {LUser, LServer}) - of - {'EXIT', _Reason} -> error; - [] -> {none, []}; - [#privacy{default = Default, lists = Lists}] -> - LItems = lists:map(fun ({N, _}) -> - #xmlel{name = <<"list">>, - attrs = [{<<"name">>, N}], - children = []} - end, - Lists), - {Default, LItems} - end; -process_lists_get_db(LUser, LServer, _Active, riak) -> - case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of - {ok, #privacy{default = Default, lists = Lists}} -> - LItems = lists:map(fun ({N, _}) -> - #xmlel{name = <<"list">>, - attrs = [{<<"name">>, N}], - children = []} - end, - Lists), - {Default, LItems}; - {error, notfound} -> - {none, []}; - {error, _} -> - error - end; -process_lists_get_db(LUser, LServer, _Active, odbc) -> - Default = case catch sql_get_default_privacy_list(LUser, LServer) of - {selected, []} -> none; - {selected, [{DefName}]} -> DefName; - _ -> none - end, - case catch sql_get_privacy_list_names(LUser, LServer) of - {selected, Names} -> - LItems = lists:map(fun ({N}) -> - #xmlel{name = <<"list">>, - attrs = [{<<"name">>, N}], - children = []} - end, - Names), - {Default, LItems}; - _ -> error - end. - process_list_get(LUser, LServer, {value, Name}, Lang) -> - case process_list_get_db(LUser, LServer, Name, - gen_mod:db_type(LServer, ?MODULE)) - of + Mod = gen_mod:db_mod(LServer, ?MODULE), + case Mod:process_list_get(LUser, LServer, Name) of error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}; not_found -> {error, ?ERR_ITEM_NOT_FOUND}; Items -> @@ -218,41 +164,6 @@ process_list_get(LUser, LServer, {value, Name}, Lang) -> process_list_get(_LUser, _LServer, false, _Lang) -> {error, ?ERR_BAD_REQUEST}. -process_list_get_db(LUser, LServer, Name, mnesia) -> - case catch mnesia:dirty_read(privacy, {LUser, LServer}) - of - {'EXIT', _Reason} -> error; - [] -> not_found; - [#privacy{lists = Lists}] -> - case lists:keysearch(Name, 1, Lists) of - {value, {_, List}} -> List; - _ -> not_found - end - end; -process_list_get_db(LUser, LServer, Name, riak) -> - case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of - {ok, #privacy{lists = Lists}} -> - case lists:keysearch(Name, 1, Lists) of - {value, {_, List}} -> List; - _ -> not_found - end; - {error, notfound} -> - not_found; - {error, _} -> - error - end; -process_list_get_db(LUser, LServer, Name, odbc) -> - case catch sql_get_privacy_list_id(LUser, LServer, Name) of - {selected, []} -> not_found; - {selected, [{ID}]} -> - case catch sql_get_privacy_list_data_by_id(ID, LServer) of - {selected, RItems} -> - lists:flatmap(fun raw_to_item/1, RItems); - _ -> error - end; - _ -> error - end. - item_to_xml(Item) -> Attrs1 = [{<<"action">>, action_to_list(Item#listitem.action)}, @@ -357,9 +268,8 @@ process_iq_set(_, From, _To, #iq{lang = Lang, sub_el = SubEl}) -> end. process_default_set(LUser, LServer, Value, Lang) -> - case process_default_set_db(LUser, LServer, Value, - gen_mod:db_type(LServer, ?MODULE)) - of + Mod = gen_mod:db_mod(LServer, ?MODULE), + case Mod:process_default_set(LUser, LServer, Value) of {atomic, error} -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}; {atomic, not_found} -> {error, ?ERR_ITEM_NOT_FOUND}; @@ -367,79 +277,9 @@ process_default_set(LUser, LServer, Value, Lang) -> _ -> {error, ?ERR_INTERNAL_SERVER_ERROR} end. -process_default_set_db(LUser, LServer, {value, Name}, - mnesia) -> - F = fun () -> - case mnesia:read({privacy, {LUser, LServer}}) of - [] -> not_found; - [#privacy{lists = Lists} = P] -> - case lists:keymember(Name, 1, Lists) of - true -> - mnesia:write(P#privacy{default = Name, - lists = Lists}), - ok; - false -> not_found - end - end - end, - mnesia:transaction(F); -process_default_set_db(LUser, LServer, {value, Name}, riak) -> - {atomic, - case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of - {ok, #privacy{lists = Lists} = P} -> - case lists:keymember(Name, 1, Lists) of - true -> - ejabberd_riak:put(P#privacy{default = Name, - lists = Lists}, - privacy_schema()); - false -> - not_found - end; - {error, _} -> - not_found - end}; -process_default_set_db(LUser, LServer, {value, Name}, - odbc) -> - F = fun () -> - case sql_get_privacy_list_names_t(LUser) of - {selected, []} -> not_found; - {selected, Names} -> - case lists:member({Name}, Names) of - true -> sql_set_default_privacy_list(LUser, Name), ok; - false -> not_found - end - end - end, - odbc_queries:sql_transaction(LServer, F); -process_default_set_db(LUser, LServer, false, mnesia) -> - F = fun () -> - case mnesia:read({privacy, {LUser, LServer}}) of - [] -> ok; - [R] -> mnesia:write(R#privacy{default = none}) - end - end, - mnesia:transaction(F); -process_default_set_db(LUser, LServer, false, riak) -> - {atomic, - case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of - {ok, R} -> - ejabberd_riak:put(R#privacy{default = none}, privacy_schema()); - {error, _} -> - ok - end}; -process_default_set_db(LUser, LServer, false, odbc) -> - case catch sql_unset_default_privacy_list(LUser, - LServer) - of - {'EXIT', _Reason} -> {atomic, error}; - {error, _Reason} -> {atomic, error}; - _ -> {atomic, ok} - end. - process_active_set(LUser, LServer, {value, Name}) -> - case process_active_set(LUser, LServer, Name, - gen_mod:db_type(LServer, ?MODULE)) - of + Mod = gen_mod:db_mod(LServer, ?MODULE), + case Mod:process_active_set(LUser, LServer, Name) of error -> {error, ?ERR_ITEM_NOT_FOUND}; Items -> NeedDb = is_list_needdb(Items), @@ -449,157 +289,16 @@ process_active_set(LUser, LServer, {value, Name}) -> process_active_set(_LUser, _LServer, false) -> {result, [], #userlist{}}. -process_active_set(LUser, LServer, Name, mnesia) -> - case catch mnesia:dirty_read(privacy, {LUser, LServer}) - of - [] -> error; - [#privacy{lists = Lists}] -> - case lists:keysearch(Name, 1, Lists) of - {value, {_, List}} -> List; - false -> error - end - end; -process_active_set(LUser, LServer, Name, riak) -> - case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of - {ok, #privacy{lists = Lists}} -> - case lists:keysearch(Name, 1, Lists) of - {value, {_, List}} -> List; - false -> error - end; - {error, _} -> - error - end; -process_active_set(LUser, LServer, Name, odbc) -> - case catch sql_get_privacy_list_id(LUser, LServer, Name) of - {selected, []} -> error; - {selected, [{ID}]} -> - case catch sql_get_privacy_list_data_by_id(ID, LServer) of - {selected, RItems} -> - lists:flatmap(fun raw_to_item/1, RItems); - _ -> error - end; - _ -> error - end. - -remove_privacy_list(LUser, LServer, Name, mnesia) -> - F = fun () -> - case mnesia:read({privacy, {LUser, LServer}}) of - [] -> ok; - [#privacy{default = Default, lists = Lists} = P] -> - if Name == Default -> conflict; - true -> - NewLists = lists:keydelete(Name, 1, Lists), - mnesia:write(P#privacy{lists = NewLists}) - end - end - end, - mnesia:transaction(F); -remove_privacy_list(LUser, LServer, Name, riak) -> - {atomic, - case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of - {ok, #privacy{default = Default, lists = Lists} = P} -> - if Name == Default -> - conflict; - true -> - NewLists = lists:keydelete(Name, 1, Lists), - ejabberd_riak:put(P#privacy{lists = NewLists}, - privacy_schema()) - end; - {error, _} -> - ok - end}; -remove_privacy_list(LUser, LServer, Name, odbc) -> - F = fun () -> - case sql_get_default_privacy_list_t(LUser) of - {selected, []} -> - sql_remove_privacy_list(LUser, Name), ok; - {selected, [{Default}]} -> - if Name == Default -> conflict; - true -> sql_remove_privacy_list(LUser, Name), ok - end - end - end, - odbc_queries:sql_transaction(LServer, F). - set_privacy_list(#privacy{us = {_, LServer}} = Privacy) -> - DBType = gen_mod:db_type(LServer, ?MODULE), - set_privacy_list(Privacy, DBType). - -set_privacy_list(Privacy, mnesia) -> - mnesia:dirty_write(Privacy); -set_privacy_list(Privacy, riak) -> - ejabberd_riak:put(Privacy, privacy_schema()); -set_privacy_list(#privacy{us = {LUser, LServer}, - default = Default, - lists = Lists}, odbc) -> - F = fun() -> - lists:foreach( - fun({Name, List}) -> - sql_add_privacy_list(LUser, Name), - {selected, [<<"id">>], [[I]]} = - sql_get_privacy_list_id_t(LUser, Name), - RItems = lists:map(fun item_to_raw/1, List), - sql_set_privacy_list(I, RItems), - if is_binary(Default) -> - sql_set_default_privacy_list(LUser, Default), - ok; - true -> - ok - end - end, Lists) - end, - odbc_queries:sql_transaction(LServer, F). - -set_privacy_list(LUser, LServer, Name, List, mnesia) -> - F = fun () -> - case mnesia:wread({privacy, {LUser, LServer}}) of - [] -> - NewLists = [{Name, List}], - mnesia:write(#privacy{us = {LUser, LServer}, - lists = NewLists}); - [#privacy{lists = Lists} = P] -> - NewLists1 = lists:keydelete(Name, 1, Lists), - NewLists = [{Name, List} | NewLists1], - mnesia:write(P#privacy{lists = NewLists}) - end - end, - mnesia:transaction(F); -set_privacy_list(LUser, LServer, Name, List, riak) -> - {atomic, - case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of - {ok, #privacy{lists = Lists} = P} -> - NewLists1 = lists:keydelete(Name, 1, Lists), - NewLists = [{Name, List} | NewLists1], - ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema()); - {error, _} -> - NewLists = [{Name, List}], - ejabberd_riak:put(#privacy{us = {LUser, LServer}, - lists = NewLists}, - privacy_schema()) - end}; -set_privacy_list(LUser, LServer, Name, List, odbc) -> - RItems = lists:map(fun item_to_raw/1, List), - F = fun () -> - ID = case sql_get_privacy_list_id_t(LUser, Name) of - {selected, []} -> - sql_add_privacy_list(LUser, Name), - {selected, [{I}]} = - sql_get_privacy_list_id_t(LUser, Name), - I; - {selected, [{I}]} -> I - end, - sql_set_privacy_list(ID, RItems), - ok - end, - odbc_queries:sql_transaction(LServer, F). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:set_privacy_list(Privacy). process_list_set(LUser, LServer, {value, Name}, Els, Lang) -> case parse_items(Els) of false -> {error, ?ERR_BAD_REQUEST}; remove -> - case remove_privacy_list(LUser, LServer, Name, - gen_mod:db_type(LServer, ?MODULE)) - of + Mod = gen_mod:db_mod(LServer, ?MODULE), + case Mod:remove_privacy_list(LUser, LServer, Name) of {atomic, conflict} -> Txt = <<"Cannot remove default list">>, {error, ?ERRT_CONFLICT(Lang, Txt)}; @@ -615,9 +314,8 @@ process_list_set(LUser, LServer, {value, Name}, Els, Lang) -> _ -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)} end; List -> - case set_privacy_list(LUser, LServer, Name, List, - gen_mod:db_type(LServer, ?MODULE)) - of + Mod = gen_mod:db_mod(LServer, ?MODULE), + case Mod:set_privacy_list(LUser, LServer, Name, List) of {atomic, ok} -> NeedDb = is_list_needdb(List), ejabberd_sm:route(jid:make(LUser, LServer, @@ -737,105 +435,20 @@ is_list_needdb(Items) -> end, Items). -get_user_list(Acc, User, Server) -> +get_user_list(_Acc, User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - {Default, Items} = get_user_list(Acc, LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)), + Mod = gen_mod:db_mod(LServer, ?MODULE), + {Default, Items} = Mod:get_user_list(LUser, LServer), NeedDb = is_list_needdb(Items), #userlist{name = Default, list = Items, needdb = NeedDb}. -get_user_list(_, LUser, LServer, mnesia) -> - case catch mnesia:dirty_read(privacy, {LUser, LServer}) - of - [] -> {none, []}; - [#privacy{default = Default, lists = Lists}] -> - case Default of - none -> {none, []}; - _ -> - case lists:keysearch(Default, 1, Lists) of - {value, {_, List}} -> {Default, List}; - _ -> {none, []} - end - end; - _ -> {none, []} - end; -get_user_list(_, LUser, LServer, riak) -> - case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of - {ok, #privacy{default = Default, lists = Lists}} -> - case Default of - none -> {none, []}; - _ -> - case lists:keysearch(Default, 1, Lists) of - {value, {_, List}} -> {Default, List}; - _ -> {none, []} - end - end; - {error, _} -> - {none, []} - end; -get_user_list(_, LUser, LServer, odbc) -> - case catch sql_get_default_privacy_list(LUser, LServer) - of - {selected, []} -> {none, []}; - {selected, [{Default}]} -> - case catch sql_get_privacy_list_data(LUser, LServer, - Default) of - {selected, RItems} -> - {Default, lists:flatmap(fun raw_to_item/1, RItems)}; - _ -> {none, []} - end; - _ -> {none, []} - end. - get_user_lists(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - get_user_lists(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)). - -get_user_lists(LUser, LServer, mnesia) -> - case catch mnesia:dirty_read(privacy, {LUser, LServer}) of - [#privacy{} = P] -> - {ok, P}; - _ -> - error - end; -get_user_lists(LUser, LServer, riak) -> - case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of - {ok, #privacy{} = P} -> - {ok, P}; - {error, _} -> - error - end; -get_user_lists(LUser, LServer, odbc) -> - Default = case catch sql_get_default_privacy_list(LUser, LServer) of - {selected, []} -> - none; - {selected, [{DefName}]} -> - DefName; - _ -> - none - end, - case catch sql_get_privacy_list_names(LUser, LServer) of - {selected, Names} -> - Lists = - lists:flatmap( - fun({Name}) -> - case catch sql_get_privacy_list_data( - LUser, LServer, Name) of - {selected, RItems} -> - [{Name, lists:flatmap(fun raw_to_item/1, RItems)}]; - _ -> - [] - end - end, Names), - {ok, #privacy{default = Default, - us = {LUser, LServer}, - lists = Lists}}; - _ -> - error - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:get_user_lists(LUser, LServer). %% From is the sender, To is the destination. %% If Dir = out, User@Server is the sender account (From). @@ -959,17 +572,8 @@ is_type_match(Type, Value, JID, Subscription, Groups) -> remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - remove_user(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). - -remove_user(LUser, LServer, mnesia) -> - F = fun () -> mnesia:delete({privacy, {LUser, LServer}}) - end, - mnesia:transaction(F); -remove_user(LUser, LServer, riak) -> - {atomic, ejabberd_riak:delete(privacy, {LUser, LServer})}; -remove_user(LUser, LServer, odbc) -> - sql_del_privacy_lists(LUser, LServer). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:remove_user(LUser, LServer). updated_list(_, #userlist{name = OldName} = Old, #userlist{name = NewName} = New) -> @@ -977,250 +581,17 @@ updated_list(_, #userlist{name = OldName} = Old, true -> Old end. -raw_to_item({SType, SValue, SAction, Order, MatchAll, - MatchIQ, MatchMessage, MatchPresenceIn, - MatchPresenceOut} = Row) -> - try - {Type, Value} = case SType of - <<"n">> -> {none, none}; - <<"j">> -> - case jid:from_string(SValue) of - #jid{} = JID -> - {jid, jid:tolower(JID)} - end; - <<"g">> -> {group, SValue}; - <<"s">> -> - case SValue of - <<"none">> -> {subscription, none}; - <<"both">> -> {subscription, both}; - <<"from">> -> {subscription, from}; - <<"to">> -> {subscription, to} - end - end, - Action = case SAction of - <<"a">> -> allow; - <<"d">> -> deny - end, - [#listitem{type = Type, value = Value, action = Action, - order = Order, match_all = MatchAll, match_iq = MatchIQ, - match_message = MatchMessage, - match_presence_in = MatchPresenceIn, - match_presence_out = MatchPresenceOut}] - catch _:_ -> - ?WARNING_MSG("failed to parse row: ~p", [Row]), - [] - end. - -item_to_raw(#listitem{type = Type, value = Value, - action = Action, order = Order, match_all = MatchAll, - match_iq = MatchIQ, match_message = MatchMessage, - match_presence_in = MatchPresenceIn, - match_presence_out = MatchPresenceOut}) -> - {SType, SValue} = case Type of - none -> {<<"n">>, <<"">>}; - jid -> - {<<"j">>, - ejabberd_odbc:escape(jid:to_string(Value))}; - group -> {<<"g">>, ejabberd_odbc:escape(Value)}; - subscription -> - case Value of - none -> {<<"s">>, <<"none">>}; - both -> {<<"s">>, <<"both">>}; - from -> {<<"s">>, <<"from">>}; - to -> {<<"s">>, <<"to">>} - end - end, - SAction = case Action of - allow -> <<"a">>; - deny -> <<"d">> - end, - {SType, SValue, SAction, Order, MatchAll, MatchIQ, - MatchMessage, MatchPresenceIn, MatchPresenceOut}. - -sql_get_default_privacy_list(LUser, LServer) -> - odbc_queries:get_default_privacy_list(LServer, LUser). - -sql_get_default_privacy_list_t(LUser) -> - odbc_queries:get_default_privacy_list_t(LUser). - -sql_get_privacy_list_names(LUser, LServer) -> - odbc_queries:get_privacy_list_names(LServer, LUser). - -sql_get_privacy_list_names_t(LUser) -> - odbc_queries:get_privacy_list_names_t(LUser). - -sql_get_privacy_list_id(LUser, LServer, Name) -> - odbc_queries:get_privacy_list_id(LServer, LUser, Name). - -sql_get_privacy_list_id_t(LUser, Name) -> - odbc_queries:get_privacy_list_id_t(LUser, Name). - -sql_get_privacy_list_data(LUser, LServer, Name) -> - odbc_queries:get_privacy_list_data(LServer, LUser, Name). - -sql_get_privacy_list_data_t(LUser, Name) -> - Username = ejabberd_odbc:escape(LUser), - SName = ejabberd_odbc:escape(Name), - odbc_queries:get_privacy_list_data_t(Username, SName). - -sql_get_privacy_list_data_by_id(ID, LServer) -> - odbc_queries:get_privacy_list_data_by_id(LServer, ID). - -sql_get_privacy_list_data_by_id_t(ID) -> - odbc_queries:get_privacy_list_data_by_id_t(ID). - -sql_set_default_privacy_list(LUser, Name) -> - odbc_queries:set_default_privacy_list(LUser, Name). - -sql_unset_default_privacy_list(LUser, LServer) -> - odbc_queries:unset_default_privacy_list(LServer, LUser). - -sql_remove_privacy_list(LUser, Name) -> - odbc_queries:remove_privacy_list(LUser, Name). - -sql_add_privacy_list(LUser, Name) -> - odbc_queries:add_privacy_list(LUser, Name). - -sql_set_privacy_list(ID, RItems) -> - odbc_queries:set_privacy_list(ID, RItems). - -sql_del_privacy_lists(LUser, LServer) -> - odbc_queries:del_privacy_lists(LServer, LUser). - -update_table() -> - Fields = record_info(fields, privacy), - case mnesia:table_info(privacy, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - privacy, Fields, set, - fun(#privacy{us = {U, _}}) -> U end, - fun(#privacy{us = {U, S}, default = Def, lists = Lists} = R) -> - NewLists = - lists:map( - fun({Name, Ls}) -> - NewLs = - lists:map( - fun(#listitem{value = Val} = L) -> - NewVal = - case Val of - {LU, LS, LR} -> - {iolist_to_binary(LU), - iolist_to_binary(LS), - iolist_to_binary(LR)}; - none -> none; - both -> both; - from -> from; - to -> to; - _ -> iolist_to_binary(Val) - end, - L#listitem{value = NewVal} - end, Ls), - {iolist_to_binary(Name), NewLs} - end, Lists), - NewDef = case Def of - none -> none; - _ -> iolist_to_binary(Def) - end, - NewUS = {iolist_to_binary(U), iolist_to_binary(S)}, - R#privacy{us = NewUS, default = NewDef, - lists = NewLists} - end); - _ -> - ?INFO_MSG("Recreating privacy table", []), - mnesia:transform_table(privacy, ignore, Fields) - end. - -export(Server) -> - case catch ejabberd_odbc:sql_query(jid:nameprep(Server), - [<<"select id from privacy_list order by " - "id desc limit 1;">>]) of - {selected, [<<"id">>], [[I]]} -> - put(id, jlib:binary_to_integer(I)); - _ -> - put(id, 0) - end, - [{privacy, - fun(Host, #privacy{us = {LUser, LServer}, lists = Lists, - default = Default}) - when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - if Default /= none -> - SDefault = ejabberd_odbc:escape(Default), - [[<<"delete from privacy_default_list where ">>, - <<"username='">>, Username, <<"';">>], - [<<"insert into privacy_default_list(username, " - "name) ">>, - <<"values ('">>, Username, <<"', '">>, - SDefault, <<"');">>]]; - true -> - [] - end ++ - lists:flatmap( - fun({Name, List}) -> - SName = ejabberd_odbc:escape(Name), - RItems = lists:map(fun item_to_raw/1, List), - ID = jlib:integer_to_binary(get_id()), - [[<<"delete from privacy_list where username='">>, - Username, <<"' and name='">>, - SName, <<"';">>], - [<<"insert into privacy_list(username, " - "name, id) values ('">>, - Username, <<"', '">>, SName, - <<"', '">>, ID, <<"');">>], - [<<"delete from privacy_list_data where " - "id='">>, ID, <<"';">>]] ++ - [[<<"insert into privacy_list_data(id, t, " - "value, action, ord, match_all, match_iq, " - "match_message, match_presence_in, " - "match_presence_out) values ('">>, - ID, <<"', '">>, str:join(Items, <<"', '">>), - <<"');">>] || Items <- RItems] - end, - Lists); - (_Host, _R) -> - [] - end}]. - -get_id() -> - ID = get(id), - put(id, ID + 1), - ID + 1. +export(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:export(LServer). import(LServer) -> - [{<<"select username from privacy_list;">>, - fun([LUser]) -> - Default = case sql_get_default_privacy_list_t(LUser) of - {selected, [<<"name">>], []} -> - none; - {selected, [<<"name">>], [[DefName]]} -> - DefName; - _ -> - none - end, - {selected, [<<"name">>], Names} = - sql_get_privacy_list_names_t(LUser), - Lists = lists:flatmap( - fun([Name]) -> - case sql_get_privacy_list_data_t(LUser, Name) of - {selected, _, RItems} -> - [{Name, - lists:map(fun raw_to_item/1, - RItems)}]; - _ -> - [] - end - end, Names), - #privacy{default = Default, - us = {LUser, LServer}, - lists = Lists} - end}]. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:import(LServer). -import(_LServer, mnesia, #privacy{} = P) -> - mnesia:dirty_write(P); -import(_LServer, riak, #privacy{} = P) -> - ejabberd_riak:put(P, privacy_schema()); -import(_, _, _) -> - pass. +import(LServer, DBType, Data) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, Data). mod_opt_type(db_type) -> fun gen_mod:v_db/1; mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; diff --git a/src/mod_privacy_mnesia.erl b/src/mod_privacy_mnesia.erl new file mode 100644 index 000000000..4026b7f64 --- /dev/null +++ b/src/mod_privacy_mnesia.erl @@ -0,0 +1,198 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_privacy_mnesia). + +-behaviour(mod_privacy). + +%% API +-export([init/2, process_lists_get/2, process_list_get/3, + process_default_set/3, process_active_set/3, + remove_privacy_list/3, set_privacy_list/1, + set_privacy_list/4, get_user_list/2, get_user_lists/2, + remove_user/2, import/2]). + +-include("jlib.hrl"). +-include("mod_privacy.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + mnesia:create_table(privacy, + [{disc_copies, [node()]}, + {attributes, record_info(fields, privacy)}]), + update_table(). + +process_lists_get(LUser, LServer) -> + case catch mnesia:dirty_read(privacy, {LUser, LServer}) of + {'EXIT', _Reason} -> error; + [] -> {none, []}; + [#privacy{default = Default, lists = Lists}] -> + LItems = lists:map(fun ({N, _}) -> + #xmlel{name = <<"list">>, + attrs = [{<<"name">>, N}], + children = []} + end, Lists), + {Default, LItems} + end. + +process_list_get(LUser, LServer, Name) -> + case catch mnesia:dirty_read(privacy, {LUser, LServer}) of + {'EXIT', _Reason} -> error; + [] -> not_found; + [#privacy{lists = Lists}] -> + case lists:keysearch(Name, 1, Lists) of + {value, {_, List}} -> List; + _ -> not_found + end + end. + +process_default_set(LUser, LServer, {value, Name}) -> + F = fun () -> + case mnesia:read({privacy, {LUser, LServer}}) of + [] -> not_found; + [#privacy{lists = Lists} = P] -> + case lists:keymember(Name, 1, Lists) of + true -> + mnesia:write(P#privacy{default = Name, + lists = Lists}), + ok; + false -> not_found + end + end + end, + mnesia:transaction(F); +process_default_set(LUser, LServer, false) -> + F = fun () -> + case mnesia:read({privacy, {LUser, LServer}}) of + [] -> ok; + [R] -> mnesia:write(R#privacy{default = none}) + end + end, + mnesia:transaction(F). + +process_active_set(LUser, LServer, Name) -> + case catch mnesia:dirty_read(privacy, {LUser, LServer}) of + [] -> error; + [#privacy{lists = Lists}] -> + case lists:keysearch(Name, 1, Lists) of + {value, {_, List}} -> List; + false -> error + end + end. + +remove_privacy_list(LUser, LServer, Name) -> + F = fun () -> + case mnesia:read({privacy, {LUser, LServer}}) of + [] -> ok; + [#privacy{default = Default, lists = Lists} = P] -> + if Name == Default -> conflict; + true -> + NewLists = lists:keydelete(Name, 1, Lists), + mnesia:write(P#privacy{lists = NewLists}) + end + end + end, + mnesia:transaction(F). + +set_privacy_list(Privacy) -> + mnesia:dirty_write(Privacy). + +set_privacy_list(LUser, LServer, Name, List) -> + F = fun () -> + case mnesia:wread({privacy, {LUser, LServer}}) of + [] -> + NewLists = [{Name, List}], + mnesia:write(#privacy{us = {LUser, LServer}, + lists = NewLists}); + [#privacy{lists = Lists} = P] -> + NewLists1 = lists:keydelete(Name, 1, Lists), + NewLists = [{Name, List} | NewLists1], + mnesia:write(P#privacy{lists = NewLists}) + end + end, + mnesia:transaction(F). + +get_user_list(LUser, LServer) -> + case catch mnesia:dirty_read(privacy, {LUser, LServer}) + of + [] -> {none, []}; + [#privacy{default = Default, lists = Lists}] -> + case Default of + none -> {none, []}; + _ -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> {Default, List}; + _ -> {none, []} + end + end; + _ -> {none, []} + end. + +get_user_lists(LUser, LServer) -> + case catch mnesia:dirty_read(privacy, {LUser, LServer}) of + [#privacy{} = P] -> + {ok, P}; + _ -> + error + end. + +remove_user(LUser, LServer) -> + F = fun () -> mnesia:delete({privacy, {LUser, LServer}}) end, + mnesia:transaction(F). + +import(_LServer, #privacy{} = P) -> + mnesia:dirty_write(P). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +update_table() -> + Fields = record_info(fields, privacy), + case mnesia:table_info(privacy, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + privacy, Fields, set, + fun(#privacy{us = {U, _}}) -> U end, + fun(#privacy{us = {U, S}, default = Def, lists = Lists} = R) -> + NewLists = + lists:map( + fun({Name, Ls}) -> + NewLs = + lists:map( + fun(#listitem{value = Val} = L) -> + NewVal = + case Val of + {LU, LS, LR} -> + {iolist_to_binary(LU), + iolist_to_binary(LS), + iolist_to_binary(LR)}; + none -> none; + both -> both; + from -> from; + to -> to; + _ -> iolist_to_binary(Val) + end, + L#listitem{value = NewVal} + end, Ls), + {iolist_to_binary(Name), NewLs} + end, Lists), + NewDef = case Def of + none -> none; + _ -> iolist_to_binary(Def) + end, + NewUS = {iolist_to_binary(U), iolist_to_binary(S)}, + R#privacy{us = NewUS, default = NewDef, + lists = NewLists} + end); + _ -> + ?INFO_MSG("Recreating privacy table", []), + mnesia:transform_table(privacy, ignore, Fields) + end. diff --git a/src/mod_privacy_riak.erl b/src/mod_privacy_riak.erl new file mode 100644 index 000000000..0c43e74f9 --- /dev/null +++ b/src/mod_privacy_riak.erl @@ -0,0 +1,160 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_privacy_riak). + +-behaviour(mod_privacy). + +%% API +-export([init/2, process_lists_get/2, process_list_get/3, + process_default_set/3, process_active_set/3, + remove_privacy_list/3, set_privacy_list/1, + set_privacy_list/4, get_user_list/2, get_user_lists/2, + remove_user/2, import/2]). + +-export([privacy_schema/0]). + +-include("jlib.hrl"). +-include("mod_privacy.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +process_lists_get(LUser, LServer) -> + case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of + {ok, #privacy{default = Default, lists = Lists}} -> + LItems = lists:map(fun ({N, _}) -> + #xmlel{name = <<"list">>, + attrs = [{<<"name">>, N}], + children = []} + end, + Lists), + {Default, LItems}; + {error, notfound} -> + {none, []}; + {error, _} -> + error + end. + +process_list_get(LUser, LServer, Name) -> + case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of + {ok, #privacy{lists = Lists}} -> + case lists:keysearch(Name, 1, Lists) of + {value, {_, List}} -> List; + _ -> not_found + end; + {error, notfound} -> + not_found; + {error, _} -> + error + end. + +process_default_set(LUser, LServer, {value, Name}) -> + {atomic, + case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of + {ok, #privacy{lists = Lists} = P} -> + case lists:keymember(Name, 1, Lists) of + true -> + ejabberd_riak:put(P#privacy{default = Name, + lists = Lists}, + privacy_schema()); + false -> + not_found + end; + {error, _} -> + not_found + end}; +process_default_set(LUser, LServer, false) -> + {atomic, + case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of + {ok, R} -> + ejabberd_riak:put(R#privacy{default = none}, privacy_schema()); + {error, _} -> + ok + end}. + +process_active_set(LUser, LServer, Name) -> + case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of + {ok, #privacy{lists = Lists}} -> + case lists:keysearch(Name, 1, Lists) of + {value, {_, List}} -> List; + false -> error + end; + {error, _} -> + error + end. + +remove_privacy_list(LUser, LServer, Name) -> + {atomic, + case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of + {ok, #privacy{default = Default, lists = Lists} = P} -> + if Name == Default -> + conflict; + true -> + NewLists = lists:keydelete(Name, 1, Lists), + ejabberd_riak:put(P#privacy{lists = NewLists}, + privacy_schema()) + end; + {error, _} -> + ok + end}. + +set_privacy_list(Privacy) -> + ejabberd_riak:put(Privacy, privacy_schema()). + +set_privacy_list(LUser, LServer, Name, List) -> + {atomic, + case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of + {ok, #privacy{lists = Lists} = P} -> + NewLists1 = lists:keydelete(Name, 1, Lists), + NewLists = [{Name, List} | NewLists1], + ejabberd_riak:put(P#privacy{lists = NewLists}, privacy_schema()); + {error, _} -> + NewLists = [{Name, List}], + ejabberd_riak:put(#privacy{us = {LUser, LServer}, + lists = NewLists}, + privacy_schema()) + end}. + +get_user_list(LUser, LServer) -> + case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of + {ok, #privacy{default = Default, lists = Lists}} -> + case Default of + none -> {none, []}; + _ -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> {Default, List}; + _ -> {none, []} + end + end; + {error, _} -> + {none, []} + end. + +get_user_lists(LUser, LServer) -> + case ejabberd_riak:get(privacy, privacy_schema(), {LUser, LServer}) of + {ok, #privacy{} = P} -> + {ok, P}; + {error, _} -> + error + end. + +remove_user(LUser, LServer) -> + {atomic, ejabberd_riak:delete(privacy, {LUser, LServer})}. + +import(_LServer, #privacy{} = P) -> + ejabberd_riak:put(P, privacy_schema()). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +privacy_schema() -> + {record_info(fields, privacy), #privacy{}}. diff --git a/src/mod_privacy_sql.erl b/src/mod_privacy_sql.erl new file mode 100644 index 000000000..ffaf55188 --- /dev/null +++ b/src/mod_privacy_sql.erl @@ -0,0 +1,397 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_privacy_sql). + +-behaviour(mod_privacy). + +%% API +-export([init/2, process_lists_get/2, process_list_get/3, + process_default_set/3, process_active_set/3, + remove_privacy_list/3, set_privacy_list/1, + set_privacy_list/4, get_user_list/2, get_user_lists/2, + remove_user/2, import/1, import/2, export/1]). + +-export([item_to_raw/1, raw_to_item/1, + sql_add_privacy_list/2, + sql_get_default_privacy_list/2, + sql_get_default_privacy_list_t/1, + sql_get_privacy_list_data/3, + sql_get_privacy_list_data_by_id_t/1, + sql_get_privacy_list_id_t/2, + sql_set_default_privacy_list/2, sql_set_privacy_list/2]). + +-include("jlib.hrl"). +-include("mod_privacy.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +process_lists_get(LUser, LServer) -> + Default = case catch sql_get_default_privacy_list(LUser, LServer) of + {selected, []} -> none; + {selected, [{DefName}]} -> DefName; + _ -> none + end, + case catch sql_get_privacy_list_names(LUser, LServer) of + {selected, Names} -> + LItems = lists:map(fun ({N}) -> + #xmlel{name = <<"list">>, + attrs = [{<<"name">>, N}], + children = []} + end, + Names), + {Default, LItems}; + _ -> error + end. + +process_list_get(LUser, LServer, Name) -> + case catch sql_get_privacy_list_id(LUser, LServer, Name) of + {selected, []} -> not_found; + {selected, [{ID}]} -> + case catch sql_get_privacy_list_data_by_id(ID, LServer) of + {selected, RItems} -> + lists:flatmap(fun raw_to_item/1, RItems); + _ -> error + end; + _ -> error + end. + +process_default_set(LUser, LServer, {value, Name}) -> + F = fun () -> + case sql_get_privacy_list_names_t(LUser) of + {selected, []} -> not_found; + {selected, Names} -> + case lists:member({Name}, Names) of + true -> sql_set_default_privacy_list(LUser, Name), ok; + false -> not_found + end + end + end, + odbc_queries:sql_transaction(LServer, F); +process_default_set(LUser, LServer, false) -> + case catch sql_unset_default_privacy_list(LUser, + LServer) + of + {'EXIT', _Reason} -> {atomic, error}; + {error, _Reason} -> {atomic, error}; + _ -> {atomic, ok} + end. + +process_active_set(LUser, LServer, Name) -> + case catch sql_get_privacy_list_id(LUser, LServer, Name) of + {selected, []} -> error; + {selected, [{ID}]} -> + case catch sql_get_privacy_list_data_by_id(ID, LServer) of + {selected, RItems} -> + lists:flatmap(fun raw_to_item/1, RItems); + _ -> error + end; + _ -> error + end. + +remove_privacy_list(LUser, LServer, Name) -> + F = fun () -> + case sql_get_default_privacy_list_t(LUser) of + {selected, []} -> + sql_remove_privacy_list(LUser, Name), ok; + {selected, [{Default}]} -> + if Name == Default -> conflict; + true -> sql_remove_privacy_list(LUser, Name), ok + end + end + end, + odbc_queries:sql_transaction(LServer, F). + +set_privacy_list(#privacy{us = {LUser, LServer}, + default = Default, + lists = Lists}) -> + F = fun() -> + lists:foreach( + fun({Name, List}) -> + sql_add_privacy_list(LUser, Name), + {selected, [<<"id">>], [[I]]} = + sql_get_privacy_list_id_t(LUser, Name), + RItems = lists:map(fun item_to_raw/1, List), + sql_set_privacy_list(I, RItems), + if is_binary(Default) -> + sql_set_default_privacy_list(LUser, Default), + ok; + true -> + ok + end + end, Lists) + end, + odbc_queries:sql_transaction(LServer, F). + +set_privacy_list(LUser, LServer, Name, List) -> + RItems = lists:map(fun item_to_raw/1, List), + F = fun () -> + ID = case sql_get_privacy_list_id_t(LUser, Name) of + {selected, []} -> + sql_add_privacy_list(LUser, Name), + {selected, [{I}]} = + sql_get_privacy_list_id_t(LUser, Name), + I; + {selected, [{I}]} -> I + end, + sql_set_privacy_list(ID, RItems), + ok + end, + odbc_queries:sql_transaction(LServer, F). + +get_user_list(LUser, LServer) -> + case catch sql_get_default_privacy_list(LUser, LServer) + of + {selected, []} -> {none, []}; + {selected, [{Default}]} -> + case catch sql_get_privacy_list_data(LUser, LServer, + Default) of + {selected, RItems} -> + {Default, lists:flatmap(fun raw_to_item/1, RItems)}; + _ -> {none, []} + end; + _ -> {none, []} + end. + +get_user_lists(LUser, LServer) -> + Default = case catch sql_get_default_privacy_list(LUser, LServer) of + {selected, []} -> + none; + {selected, [{DefName}]} -> + DefName; + _ -> + none + end, + case catch sql_get_privacy_list_names(LUser, LServer) of + {selected, Names} -> + Lists = + lists:flatmap( + fun({Name}) -> + case catch sql_get_privacy_list_data( + LUser, LServer, Name) of + {selected, RItems} -> + [{Name, lists:flatmap(fun raw_to_item/1, RItems)}]; + _ -> + [] + end + end, Names), + {ok, #privacy{default = Default, + us = {LUser, LServer}, + lists = Lists}}; + _ -> + error + end. + +remove_user(LUser, LServer) -> + sql_del_privacy_lists(LUser, LServer). + +export(Server) -> + case catch ejabberd_odbc:sql_query(jid:nameprep(Server), + [<<"select id from privacy_list order by " + "id desc limit 1;">>]) of + {selected, [<<"id">>], [[I]]} -> + put(id, jlib:binary_to_integer(I)); + _ -> + put(id, 0) + end, + [{privacy, + fun(Host, #privacy{us = {LUser, LServer}, lists = Lists, + default = Default}) + when LServer == Host -> + Username = ejabberd_odbc:escape(LUser), + if Default /= none -> + SDefault = ejabberd_odbc:escape(Default), + [[<<"delete from privacy_default_list where ">>, + <<"username='">>, Username, <<"';">>], + [<<"insert into privacy_default_list(username, " + "name) ">>, + <<"values ('">>, Username, <<"', '">>, + SDefault, <<"');">>]]; + true -> + [] + end ++ + lists:flatmap( + fun({Name, List}) -> + SName = ejabberd_odbc:escape(Name), + RItems = lists:map(fun item_to_raw/1, List), + ID = jlib:integer_to_binary(get_id()), + [[<<"delete from privacy_list where username='">>, + Username, <<"' and name='">>, + SName, <<"';">>], + [<<"insert into privacy_list(username, " + "name, id) values ('">>, + Username, <<"', '">>, SName, + <<"', '">>, ID, <<"');">>], + [<<"delete from privacy_list_data where " + "id='">>, ID, <<"';">>]] ++ + [[<<"insert into privacy_list_data(id, t, " + "value, action, ord, match_all, match_iq, " + "match_message, match_presence_in, " + "match_presence_out) values ('">>, + ID, <<"', '">>, str:join(Items, <<"', '">>), + <<"');">>] || Items <- RItems] + end, + Lists); + (_Host, _R) -> + [] + end}]. + +get_id() -> + ID = get(id), + put(id, ID + 1), + ID + 1. + +import(LServer) -> + [{<<"select username from privacy_list;">>, + fun([LUser]) -> + Default = case sql_get_default_privacy_list_t(LUser) of + {selected, [<<"name">>], []} -> + none; + {selected, [<<"name">>], [[DefName]]} -> + DefName; + _ -> + none + end, + {selected, [<<"name">>], Names} = + sql_get_privacy_list_names_t(LUser), + Lists = lists:flatmap( + fun([Name]) -> + case sql_get_privacy_list_data_t(LUser, Name) of + {selected, _, RItems} -> + [{Name, + lists:map(fun raw_to_item/1, + RItems)}]; + _ -> + [] + end + end, Names), + #privacy{default = Default, + us = {LUser, LServer}, + lists = Lists} + end}]. + +import(_, _) -> + pass. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +raw_to_item({SType, SValue, SAction, Order, MatchAll, + MatchIQ, MatchMessage, MatchPresenceIn, + MatchPresenceOut} = Row) -> + try + {Type, Value} = case SType of + <<"n">> -> {none, none}; + <<"j">> -> + case jid:from_string(SValue) of + #jid{} = JID -> + {jid, jid:tolower(JID)} + end; + <<"g">> -> {group, SValue}; + <<"s">> -> + case SValue of + <<"none">> -> {subscription, none}; + <<"both">> -> {subscription, both}; + <<"from">> -> {subscription, from}; + <<"to">> -> {subscription, to} + end + end, + Action = case SAction of + <<"a">> -> allow; + <<"d">> -> deny + end, + [#listitem{type = Type, value = Value, action = Action, + order = Order, match_all = MatchAll, match_iq = MatchIQ, + match_message = MatchMessage, + match_presence_in = MatchPresenceIn, + match_presence_out = MatchPresenceOut}] + catch _:_ -> + ?WARNING_MSG("failed to parse row: ~p", [Row]), + [] + end. + +item_to_raw(#listitem{type = Type, value = Value, + action = Action, order = Order, match_all = MatchAll, + match_iq = MatchIQ, match_message = MatchMessage, + match_presence_in = MatchPresenceIn, + match_presence_out = MatchPresenceOut}) -> + {SType, SValue} = case Type of + none -> {<<"n">>, <<"">>}; + jid -> + {<<"j">>, + ejabberd_odbc:escape(jid:to_string(Value))}; + group -> {<<"g">>, ejabberd_odbc:escape(Value)}; + subscription -> + case Value of + none -> {<<"s">>, <<"none">>}; + both -> {<<"s">>, <<"both">>}; + from -> {<<"s">>, <<"from">>}; + to -> {<<"s">>, <<"to">>} + end + end, + SAction = case Action of + allow -> <<"a">>; + deny -> <<"d">> + end, + {SType, SValue, SAction, Order, MatchAll, MatchIQ, + MatchMessage, MatchPresenceIn, MatchPresenceOut}. + +sql_get_default_privacy_list(LUser, LServer) -> + odbc_queries:get_default_privacy_list(LServer, LUser). + +sql_get_default_privacy_list_t(LUser) -> + odbc_queries:get_default_privacy_list_t(LUser). + +sql_get_privacy_list_names(LUser, LServer) -> + odbc_queries:get_privacy_list_names(LServer, LUser). + +sql_get_privacy_list_names_t(LUser) -> + odbc_queries:get_privacy_list_names_t(LUser). + +sql_get_privacy_list_id(LUser, LServer, Name) -> + odbc_queries:get_privacy_list_id(LServer, LUser, Name). + +sql_get_privacy_list_id_t(LUser, Name) -> + odbc_queries:get_privacy_list_id_t(LUser, Name). + +sql_get_privacy_list_data(LUser, LServer, Name) -> + odbc_queries:get_privacy_list_data(LServer, LUser, Name). + +sql_get_privacy_list_data_t(LUser, Name) -> + Username = ejabberd_odbc:escape(LUser), + SName = ejabberd_odbc:escape(Name), + odbc_queries:get_privacy_list_data_t(Username, SName). + +sql_get_privacy_list_data_by_id(ID, LServer) -> + odbc_queries:get_privacy_list_data_by_id(LServer, ID). + +sql_get_privacy_list_data_by_id_t(ID) -> + odbc_queries:get_privacy_list_data_by_id_t(ID). + +sql_set_default_privacy_list(LUser, Name) -> + odbc_queries:set_default_privacy_list(LUser, Name). + +sql_unset_default_privacy_list(LUser, LServer) -> + odbc_queries:unset_default_privacy_list(LServer, LUser). + +sql_remove_privacy_list(LUser, Name) -> + odbc_queries:remove_privacy_list(LUser, Name). + +sql_add_privacy_list(LUser, Name) -> + odbc_queries:add_privacy_list(LUser, Name). + +sql_set_privacy_list(ID, RItems) -> + odbc_queries:set_privacy_list(ID, RItems). + +sql_del_privacy_lists(LUser, LServer) -> + odbc_queries:del_privacy_lists(LServer, LUser). From 860db2ddcae5d8bc541a566de24e5c03e546292d Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Thu, 14 Apr 2016 14:17:20 +0300 Subject: [PATCH 13/15] Clean mod_blocking.erl from DB specific code --- src/mod_blocking.erl | 222 +++--------------------------------- src/mod_blocking_mnesia.erl | 85 ++++++++++++++ src/mod_blocking_riak.erl | 98 ++++++++++++++++ src/mod_blocking_sql.erl | 87 ++++++++++++++ 4 files changed, 285 insertions(+), 207 deletions(-) create mode 100644 src/mod_blocking_mnesia.erl create mode 100644 src/mod_blocking_riak.erl create mode 100644 src/mod_blocking_sql.erl diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl index d94b3090f..af06e650d 100644 --- a/src/mod_blocking.erl +++ b/src/mod_blocking.erl @@ -39,6 +39,10 @@ -include("mod_privacy.hrl"). +-callback process_blocklist_block(binary(), binary(), function()) -> {atomic, any()}. +-callback unblock_by_filter(binary(), binary(), function()) -> {atomic, any()}. +-callback process_blocklist_get(binary(), binary()) -> [listitem()] | error. + start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, one_queue), @@ -147,9 +151,8 @@ process_blocklist_block(LUser, LServer, JIDs, Lang) -> end, List, JIDs) end, - case process_blocklist_block_db(LUser, LServer, Filter, - gen_mod:db_type(LServer, mod_privacy)) - of + Mod = db_mod(LServer), + case Mod:process_blocklist_block(LUser, LServer, Filter) of {atomic, {ok, Default, List}} -> UserList = make_userlist(Default, List), broadcast_list_update(LUser, LServer, Default, @@ -162,102 +165,14 @@ process_blocklist_block(LUser, LServer, JIDs, Lang) -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)} end. -process_blocklist_block_db(LUser, LServer, Filter, - mnesia) -> - F = fun () -> - case mnesia:wread({privacy, {LUser, LServer}}) of - [] -> - P = #privacy{us = {LUser, LServer}}, - NewDefault = <<"Blocked contacts">>, - NewLists1 = [], - List = []; - [#privacy{default = Default, lists = Lists} = P] -> - case lists:keysearch(Default, 1, Lists) of - {value, {_, List}} -> - NewDefault = Default, - NewLists1 = lists:keydelete(Default, 1, Lists); - false -> - NewDefault = <<"Blocked contacts">>, - NewLists1 = Lists, - List = [] - end - end, - NewList = Filter(List), - NewLists = [{NewDefault, NewList} | NewLists1], - mnesia:write(P#privacy{default = NewDefault, - lists = NewLists}), - {ok, NewDefault, NewList} - end, - mnesia:transaction(F); -process_blocklist_block_db(LUser, LServer, Filter, - riak) -> - {atomic, - begin - case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(), - {LUser, LServer}) of - {ok, #privacy{default = Default, lists = Lists} = P} -> - case lists:keysearch(Default, 1, Lists) of - {value, {_, List}} -> - NewDefault = Default, - NewLists1 = lists:keydelete(Default, 1, Lists); - false -> - NewDefault = <<"Blocked contacts">>, - NewLists1 = Lists, - List = [] - end; - {error, _} -> - P = #privacy{us = {LUser, LServer}}, - NewDefault = <<"Blocked contacts">>, - NewLists1 = [], - List = [] - end, - NewList = Filter(List), - NewLists = [{NewDefault, NewList} | NewLists1], - case ejabberd_riak:put(P#privacy{default = NewDefault, - lists = NewLists}, - mod_privacy:privacy_schema()) of - ok -> - {ok, NewDefault, NewList}; - Err -> - Err - end - end}; -process_blocklist_block_db(LUser, LServer, Filter, odbc) -> - F = fun () -> - Default = case - mod_privacy:sql_get_default_privacy_list_t(LUser) - of - {selected, []} -> - Name = <<"Blocked contacts">>, - mod_privacy:sql_add_privacy_list(LUser, Name), - mod_privacy:sql_set_default_privacy_list(LUser, - Name), - Name; - {selected, [{Name}]} -> Name - end, - {selected, [{ID}]} = - mod_privacy:sql_get_privacy_list_id_t(LUser, Default), - case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of - {selected, RItems = [_ | _]} -> - List = lists:flatmap(fun mod_privacy:raw_to_item/1, RItems); - _ -> List = [] - end, - NewList = Filter(List), - NewRItems = lists:map(fun mod_privacy:item_to_raw/1, - NewList), - mod_privacy:sql_set_privacy_list(ID, NewRItems), - {ok, Default, NewList} - end, - ejabberd_odbc:sql_transaction(LServer, F). - process_blocklist_unblock_all(LUser, LServer, Lang) -> Filter = fun (List) -> lists:filter(fun (#listitem{action = A}) -> A =/= deny end, List) end, - DBType = gen_mod:db_type(LServer, mod_privacy), - case unblock_by_filter(LUser, LServer, Filter, DBType) of + Mod = db_mod(LServer), + case Mod:unblock_by_filter(LUser, LServer, Filter) of {atomic, ok} -> {result, []}; {atomic, {ok, Default, List}} -> UserList = make_userlist(Default, List), @@ -279,8 +194,8 @@ process_blocklist_unblock(LUser, LServer, JIDs, Lang) -> end, List) end, - DBType = gen_mod:db_type(LServer, mod_privacy), - case unblock_by_filter(LUser, LServer, Filter, DBType) of + Mod = db_mod(LServer), + case Mod:unblock_by_filter(LUser, LServer, Filter) of {atomic, ok} -> {result, []}; {atomic, {ok, Default, List}} -> UserList = make_userlist(Default, List), @@ -294,76 +209,6 @@ process_blocklist_unblock(LUser, LServer, JIDs, Lang) -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)} end. -unblock_by_filter(LUser, LServer, Filter, mnesia) -> - F = fun () -> - case mnesia:read({privacy, {LUser, LServer}}) of - [] -> - % No lists, nothing to unblock - ok; - [#privacy{default = Default, lists = Lists} = P] -> - case lists:keysearch(Default, 1, Lists) of - {value, {_, List}} -> - NewList = Filter(List), - NewLists1 = lists:keydelete(Default, 1, Lists), - NewLists = [{Default, NewList} | NewLists1], - mnesia:write(P#privacy{lists = NewLists}), - {ok, Default, NewList}; - false -> - % No default list, nothing to unblock - ok - end - end - end, - mnesia:transaction(F); -unblock_by_filter(LUser, LServer, Filter, riak) -> - {atomic, - case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(), - {LUser, LServer}) of - {error, _} -> - %% No lists, nothing to unblock - ok; - {ok, #privacy{default = Default, lists = Lists} = P} -> - case lists:keysearch(Default, 1, Lists) of - {value, {_, List}} -> - NewList = Filter(List), - NewLists1 = lists:keydelete(Default, 1, Lists), - NewLists = [{Default, NewList} | NewLists1], - case ejabberd_riak:put(P#privacy{lists = NewLists}, - mod_privacy:privacy_schema()) of - ok -> - {ok, Default, NewList}; - Err -> - Err - end; - false -> - %% No default list, nothing to unblock - ok - end - end}; -unblock_by_filter(LUser, LServer, Filter, odbc) -> - F = fun () -> - case mod_privacy:sql_get_default_privacy_list_t(LUser) - of - {selected, []} -> ok; - {selected, [{Default}]} -> - {selected, [{ID}]} = - mod_privacy:sql_get_privacy_list_id_t(LUser, Default), - case mod_privacy:sql_get_privacy_list_data_by_id_t(ID) of - {selected, RItems = [_ | _]} -> - List = lists:flatmap(fun mod_privacy:raw_to_item/1, - RItems), - NewList = Filter(List), - NewRItems = lists:map(fun mod_privacy:item_to_raw/1, - NewList), - mod_privacy:sql_set_privacy_list(ID, NewRItems), - {ok, Default, NewList}; - _ -> ok - end; - _ -> ok - end - end, - ejabberd_odbc:sql_transaction(LServer, F). - make_userlist(Name, List) -> NeedDb = mod_privacy:is_list_needdb(List), #userlist{name = Name, list = List, needdb = NeedDb}. @@ -380,9 +225,8 @@ broadcast_blocklist_event(LUser, LServer, Event) -> {broadcast, {blocking, Event}}). process_blocklist_get(LUser, LServer, Lang) -> - case process_blocklist_get_db(LUser, LServer, - gen_mod:db_type(LServer, mod_privacy)) - of + Mod = db_mod(LServer), + case Mod:process_blocklist_get(LUser, LServer) of error -> {error, ?ERRT_INTERNAL_SERVER_ERROR(Lang, <<"Database failure">>)}; List -> @@ -402,45 +246,9 @@ process_blocklist_get(LUser, LServer, Lang) -> children = Items}]} end. -process_blocklist_get_db(LUser, LServer, mnesia) -> - case catch mnesia:dirty_read(privacy, {LUser, LServer}) - of - {'EXIT', _Reason} -> error; - [] -> []; - [#privacy{default = Default, lists = Lists}] -> - case lists:keysearch(Default, 1, Lists) of - {value, {_, List}} -> List; - _ -> [] - end - end; -process_blocklist_get_db(LUser, LServer, riak) -> - case ejabberd_riak:get(privacy, mod_privacy:privacy_schema(), - {LUser, LServer}) of - {ok, #privacy{default = Default, lists = Lists}} -> - case lists:keysearch(Default, 1, Lists) of - {value, {_, List}} -> List; - _ -> [] - end; - {error, notfound} -> - []; - {error, _} -> - error - end; -process_blocklist_get_db(LUser, LServer, odbc) -> - case catch - mod_privacy:sql_get_default_privacy_list(LUser, LServer) - of - {selected, []} -> []; - {selected, [{Default}]} -> - case catch mod_privacy:sql_get_privacy_list_data(LUser, - LServer, Default) - of - {selected, RItems} -> - lists:flatmap(fun mod_privacy:raw_to_item/1, RItems); - {'EXIT', _} -> error - end; - {'EXIT', _} -> error - end. +db_mod(LServer) -> + DBType = gen_mod:db_type(LServer, mod_privacy), + gen_mod:db_mod(DBType, ?MODULE). mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1; mod_opt_type(_) -> [iqdisc]. diff --git a/src/mod_blocking_mnesia.erl b/src/mod_blocking_mnesia.erl new file mode 100644 index 000000000..5a4bde64c --- /dev/null +++ b/src/mod_blocking_mnesia.erl @@ -0,0 +1,85 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_blocking_mnesia). + +-behaviour(mod_blocking). + +%% API +-export([process_blocklist_block/3, unblock_by_filter/3, + process_blocklist_get/2]). + +-include("jlib.hrl"). +-include("mod_privacy.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +process_blocklist_block(LUser, LServer, Filter) -> + F = fun () -> + case mnesia:wread({privacy, {LUser, LServer}}) of + [] -> + P = #privacy{us = {LUser, LServer}}, + NewDefault = <<"Blocked contacts">>, + NewLists1 = [], + List = []; + [#privacy{default = Default, lists = Lists} = P] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + NewDefault = Default, + NewLists1 = lists:keydelete(Default, 1, Lists); + false -> + NewDefault = <<"Blocked contacts">>, + NewLists1 = Lists, + List = [] + end + end, + NewList = Filter(List), + NewLists = [{NewDefault, NewList} | NewLists1], + mnesia:write(P#privacy{default = NewDefault, + lists = NewLists}), + {ok, NewDefault, NewList} + end, + mnesia:transaction(F). + +unblock_by_filter(LUser, LServer, Filter) -> + F = fun () -> + case mnesia:read({privacy, {LUser, LServer}}) of + [] -> + %% No lists, nothing to unblock + ok; + [#privacy{default = Default, lists = Lists} = P] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + NewList = Filter(List), + NewLists1 = lists:keydelete(Default, 1, Lists), + NewLists = [{Default, NewList} | NewLists1], + mnesia:write(P#privacy{lists = NewLists}), + {ok, Default, NewList}; + false -> + %% No default list, nothing to unblock + ok + end + end + end, + mnesia:transaction(F). + +process_blocklist_get(LUser, LServer) -> + case catch mnesia:dirty_read(privacy, {LUser, LServer}) of + {'EXIT', _Reason} -> error; + [] -> []; + [#privacy{default = Default, lists = Lists}] -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> List; + _ -> [] + end + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/mod_blocking_riak.erl b/src/mod_blocking_riak.erl new file mode 100644 index 000000000..5dd5cfa92 --- /dev/null +++ b/src/mod_blocking_riak.erl @@ -0,0 +1,98 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_blocking_riak). + +-behaviour(mod_blocking). + +%% API +-export([process_blocklist_block/3, unblock_by_filter/3, + process_blocklist_get/2]). + +-include("jlib.hrl"). +-include("mod_privacy.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +process_blocklist_block(LUser, LServer, Filter) -> + {atomic, + begin + case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(), + {LUser, LServer}) of + {ok, #privacy{default = Default, lists = Lists} = P} -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + NewDefault = Default, + NewLists1 = lists:keydelete(Default, 1, Lists); + false -> + NewDefault = <<"Blocked contacts">>, + NewLists1 = Lists, + List = [] + end; + {error, _} -> + P = #privacy{us = {LUser, LServer}}, + NewDefault = <<"Blocked contacts">>, + NewLists1 = [], + List = [] + end, + NewList = Filter(List), + NewLists = [{NewDefault, NewList} | NewLists1], + case ejabberd_riak:put(P#privacy{default = NewDefault, + lists = NewLists}, + mod_privacy_riak:privacy_schema()) of + ok -> + {ok, NewDefault, NewList}; + Err -> + Err + end + end}. + +unblock_by_filter(LUser, LServer, Filter) -> + {atomic, + case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(), + {LUser, LServer}) of + {error, _} -> + %% No lists, nothing to unblock + ok; + {ok, #privacy{default = Default, lists = Lists} = P} -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> + NewList = Filter(List), + NewLists1 = lists:keydelete(Default, 1, Lists), + NewLists = [{Default, NewList} | NewLists1], + case ejabberd_riak:put(P#privacy{lists = NewLists}, + mod_privacy_riak:privacy_schema()) of + ok -> + {ok, Default, NewList}; + Err -> + Err + end; + false -> + %% No default list, nothing to unblock + ok + end + end}. + +process_blocklist_get(LUser, LServer) -> + case ejabberd_riak:get(privacy, mod_privacy_riak:privacy_schema(), + {LUser, LServer}) of + {ok, #privacy{default = Default, lists = Lists}} -> + case lists:keysearch(Default, 1, Lists) of + {value, {_, List}} -> List; + _ -> [] + end; + {error, notfound} -> + []; + {error, _} -> + error + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== diff --git a/src/mod_blocking_sql.erl b/src/mod_blocking_sql.erl new file mode 100644 index 000000000..8177adae8 --- /dev/null +++ b/src/mod_blocking_sql.erl @@ -0,0 +1,87 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 14 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_blocking_sql). + +-behaviour(mod_blocking). + +%% API +-export([process_blocklist_block/3, unblock_by_filter/3, + process_blocklist_get/2]). + +-include("jlib.hrl"). +-include("mod_privacy.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +process_blocklist_block(LUser, LServer, Filter) -> + F = fun () -> + Default = case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of + {selected, []} -> + Name = <<"Blocked contacts">>, + mod_privacy_sql:sql_add_privacy_list(LUser, Name), + mod_privacy_sql:sql_set_default_privacy_list(LUser, Name), + Name; + {selected, [{Name}]} -> Name + end, + {selected, [{ID}]} = + mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default), + case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of + {selected, RItems = [_ | _]} -> + List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems); + _ -> + List = [] + end, + NewList = Filter(List), + NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1, + NewList), + mod_privacy_sql:sql_set_privacy_list(ID, NewRItems), + {ok, Default, NewList} + end, + ejabberd_odbc:sql_transaction(LServer, F). + +unblock_by_filter(LUser, LServer, Filter) -> + F = fun () -> + case mod_privacy_sql:sql_get_default_privacy_list_t(LUser) of + {selected, []} -> ok; + {selected, [{Default}]} -> + {selected, [{ID}]} = + mod_privacy_sql:sql_get_privacy_list_id_t(LUser, Default), + case mod_privacy_sql:sql_get_privacy_list_data_by_id_t(ID) of + {selected, RItems = [_ | _]} -> + List = lists:flatmap(fun mod_privacy_sql:raw_to_item/1, + RItems), + NewList = Filter(List), + NewRItems = lists:map(fun mod_privacy_sql:item_to_raw/1, + NewList), + mod_privacy_sql:sql_set_privacy_list(ID, NewRItems), + {ok, Default, NewList}; + _ -> ok + end; + _ -> ok + end + end, + ejabberd_odbc:sql_transaction(LServer, F). + +process_blocklist_get(LUser, LServer) -> + case catch mod_privacy_sql:sql_get_default_privacy_list(LUser, LServer) of + {selected, []} -> []; + {selected, [{Default}]} -> + case catch mod_privacy_sql:sql_get_privacy_list_data( + LUser, LServer, Default) of + {selected, RItems} -> + lists:flatmap(fun mod_privacy_sql:raw_to_item/1, RItems); + {'EXIT', _} -> error + end; + {'EXIT', _} -> error + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== From 901d2e0aed83d195a4d1cf2929114b07dcac0dd8 Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Fri, 15 Apr 2016 13:44:33 +0300 Subject: [PATCH 14/15] Clean mod_offline.erl from DB specific code --- src/mod_offline.erl | 934 +++++++------------------------------ src/mod_offline_mnesia.erl | 232 +++++++++ src/mod_offline_riak.erl | 153 ++++++ src/mod_offline_sql.erl | 252 ++++++++++ 4 files changed, 808 insertions(+), 763 deletions(-) create mode 100644 src/mod_offline_mnesia.erl create mode 100644 src/mod_offline_riak.erl create mode 100644 src/mod_offline_sql.erl diff --git a/src/mod_offline.erl b/src/mod_offline.erl index 54eda165c..2cdd82ae8 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -25,8 +25,6 @@ -module(mod_offline). --compile([{parse_transform, ejabberd_sql_pt}]). - -author('alexey@process-one.net'). -protocol({xep, 13, '1.2'}). @@ -61,6 +59,7 @@ get_queue_length/2, count_offline_messages/2, get_offline_els/2, + find_x_expire/2, webadmin_page/3, webadmin_user/4, webadmin_user_parse_query/5]). @@ -82,8 +81,6 @@ -include("mod_offline.hrl"). --include("ejabberd_sql_pt.hrl"). - -define(PROCNAME, ejabberd_offline). -define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000). @@ -91,6 +88,25 @@ %% default value for the maximum number of user messages -define(MAX_USER_MESSAGES, infinity). +-type us() :: {binary(), binary()}. +-callback init(binary(), gen_mod:opts()) -> any(). +-callback import(binary(), #offline_msg{}) -> ok | pass. +-callback store_messages(binary(), us(), [#offline_msg{}], + non_neg_integer(), non_neg_integer()) -> + {atomic, any()}. +-callback pop_messages(binary(), binary()) -> + {atomic, [#offline_msg{}]} | {aborted, any()}. +-callback remove_expired_messages(binary()) -> {atomic, any()}. +-callback remove_old_messages(non_neg_integer(), binary()) -> {atomic, any()}. +-callback remove_user(binary(), binary()) -> {atomic, any()}. +-callback read_message_headers(binary(), binary()) -> any(). +-callback read_message(binary(), binary(), non_neg_integer()) -> + {ok, #offline_msg{}} | error. +-callback remove_message(binary(), binary(), non_neg_integer()) -> ok. +-callback read_all_messages(binary(), binary()) -> [#offline_msg{}]. +-callback remove_all_messages(binary(), binary()) -> {atomic, any()}. +-callback count_messages(binary(), binary()) -> non_neg_integer(). + start_link(Host, Opts) -> Proc = gen_mod:get_module_proc(Host, ?PROCNAME), ?GEN_SERVER:start_link({local, Proc}, ?MODULE, @@ -115,14 +131,8 @@ stop(Host) -> %%==================================================================== init([Host, Opts]) -> - case gen_mod:db_type(Host, Opts) of - mnesia -> - mnesia:create_table(offline_msg, - [{disc_only_copies, [node()]}, {type, bag}, - {attributes, record_info(fields, offline_msg)}]), - update_table(); - _ -> ok - end, + Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod:init(Host, Opts), IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, no_queue), ejabberd_hooks:add(offline_message_hook, Host, ?MODULE, @@ -174,7 +184,7 @@ handle_info(#offline_msg{us = UserServer} = Msg, State) -> Len = length(Msgs), MaxOfflineMsgs = get_max_user_messages(AccessMaxOfflineMsgs, UserServer, Host), - store_offline_msg(Host, UserServer, Msgs, Len, MaxOfflineMsgs, DBType), + store_offline_msg(Host, UserServer, Msgs, Len, MaxOfflineMsgs), {noreply, State}; handle_info(_Info, State) -> @@ -210,68 +220,12 @@ terminate(_Reason, State) -> code_change(_OldVsn, State, _Extra) -> {ok, State}. store_offline_msg(Host, US, Msgs, Len, MaxOfflineMsgs) -> - DBType = gen_mod:db_type(Host, ?MODULE), - store_offline_msg(Host, US, Msgs, Len, MaxOfflineMsgs, DBType). - -store_offline_msg(_Host, US, Msgs, Len, MaxOfflineMsgs, - mnesia) -> - F = fun () -> - Count = if MaxOfflineMsgs =/= infinity -> - Len + count_mnesia_records(US); - true -> 0 - end, - if Count > MaxOfflineMsgs -> discard_warn_sender(Msgs); - true -> - if Len >= (?OFFLINE_TABLE_LOCK_THRESHOLD) -> - mnesia:write_lock_table(offline_msg); - true -> ok - end, - lists:foreach(fun (M) -> mnesia:write(M) end, Msgs) - end - end, - mnesia:transaction(F); -store_offline_msg(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs, odbc) -> - Count = if MaxOfflineMsgs =/= infinity -> - Len + count_offline_messages(User, Host); - true -> 0 - end, - if Count > MaxOfflineMsgs -> discard_warn_sender(Msgs); - true -> - Query = lists:map(fun (M) -> - Username = - ejabberd_odbc:escape((M#offline_msg.to)#jid.luser), - From = M#offline_msg.from, - To = M#offline_msg.to, - Packet = - jlib:replace_from_to(From, To, - M#offline_msg.packet), - NewPacket = - jlib:add_delay_info(Packet, Host, - M#offline_msg.timestamp, - <<"Offline Storage">>), - XML = - ejabberd_odbc:escape(fxml:element_to_binary(NewPacket)), - odbc_queries:add_spool_sql(Username, XML) - end, - Msgs), - odbc_queries:add_spool(Host, Query) - end; -store_offline_msg(Host, {User, _}, Msgs, Len, MaxOfflineMsgs, - riak) -> - Count = if MaxOfflineMsgs =/= infinity -> - Len + count_offline_messages(User, Host); - true -> 0 - end, - if - Count > MaxOfflineMsgs -> - discard_warn_sender(Msgs); - true -> - lists:foreach( - fun(#offline_msg{us = US, - timestamp = TS} = M) -> - ejabberd_riak:put(M, offline_msg_schema(), - [{i, TS}, {'2i', [{<<"us">>, US}]}]) - end, Msgs) + Mod = gen_mod:db_mod(Host, ?MODULE), + case Mod:store_messages(Host, US, Msgs, Len, MaxOfflineMsgs) of + {atomic, discard} -> + discard_warn_sender(Msgs); + _ -> + ok end. get_max_user_messages(AccessRule, {User, Server}, Host) -> @@ -330,11 +284,11 @@ get_sm_items(_Acc, #jid{luser = U, lserver = S, lresource = R} = JID, BareJID = jid:to_string(jid:remove_resource(JID)), Pid ! dont_ask_offline, {result, lists:map( - fun({Node, From, _OfflineMsg}) -> + fun({Node, From, _To, _El}) -> #xmlel{name = <<"item">>, attrs = [{<<"jid">>, BareJID}, {<<"node">>, Node}, - {<<"name">>, From}]} + {<<"name">>, jid:to_string(From)}]} end, Hdrs)}; none -> {result, []} @@ -452,46 +406,31 @@ handle_offline_fetch(#jid{luser = U, lserver = S, lresource = R}) -> Pid when is_pid(Pid) -> Pid ! dont_ask_offline, lists:foreach( - fun({Node, _, Msg}) -> - case offline_msg_to_route(S, Msg) of - {route, From, To, El} -> - NewEl = set_offline_tag(El, Node), - Pid ! {route, From, To, NewEl}; - error -> - ok - end + fun({Node, From, To, El}) -> + NewEl = set_offline_tag(El, Node), + Pid ! {route, From, To, NewEl} end, read_message_headers(U, S)) end. -fetch_msg_by_node(To, <>) -> - case jid:from_string(From_s) of - From = #jid{} -> - case gen_mod:db_type(To#jid.lserver, ?MODULE) of - odbc -> - read_message(From, To, Seq, odbc); - DBType -> - case binary_to_timestamp(Seq) of - undefined -> ok; - TS -> read_message(From, To, TS, DBType) - end - end; - error -> - ok +fetch_msg_by_node(To, Seq) -> + case catch binary_to_integer(Seq) of + I when is_integer(I), I >= 0 -> + LUser = To#jid.luser, + LServer = To#jid.lserver, + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:read_message(LUser, LServer, I); + _ -> + error end. -remove_msg_by_node(To, <>) -> - case jid:from_string(From_s) of - From = #jid{} -> - case gen_mod:db_type(To#jid.lserver, ?MODULE) of - odbc -> - remove_message(From, To, Seq, odbc); - DBType -> - case binary_to_timestamp(Seq) of - undefined -> ok; - TS -> remove_message(From, To, TS, DBType) - end - end; - error -> +remove_msg_by_node(To, Seq) -> + case catch binary_to_integer(Seq) of + I when is_integer(I), I>= 0 -> + LUser = To#jid.luser, + LServer = To#jid.lserver, + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:remove_message(LUser, LServer, I); + _ -> ok end. @@ -648,21 +587,11 @@ find_x_expire(TimeStamp, [El | Els]) -> resend_offline_messages(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - US = {LUser, LServer}, - F = fun () -> - Rs = mnesia:wread({offline_msg, US}), - mnesia:delete({offline_msg, US}), - Rs - end, - case mnesia:transaction(F) of - {atomic, Rs} -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + case Mod:pop_messages(LUser, LServer) of + {ok, Rs} -> lists:foreach(fun (R) -> - ejabberd_sm ! - {route, R#offline_msg.from, R#offline_msg.to, - jlib:add_delay_info(R#offline_msg.packet, - LServer, - R#offline_msg.timestamp, - <<"Offline Storage">>)} + ejabberd_sm ! offline_msg_to_route(LServer, R) end, lists:keysort(#offline_msg.timestamp, Rs)); _ -> ok @@ -671,190 +600,47 @@ resend_offline_messages(User, Server) -> pop_offline_messages(Ls, User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - pop_offline_messages(Ls, LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). - -pop_offline_messages(Ls, LUser, LServer, mnesia) -> - US = {LUser, LServer}, - F = fun () -> - Rs = mnesia:wread({offline_msg, US}), - mnesia:delete({offline_msg, US}), - Rs - end, - case mnesia:transaction(F) of - {atomic, Rs} -> - TS = p1_time_compat:timestamp(), - Ls ++ - lists:map(fun (R) -> - offline_msg_to_route(LServer, R) - end, - lists:filter(fun (R) -> - case R#offline_msg.expire of - never -> true; - TimeStamp -> TS < TimeStamp - end - end, - lists:keysort(#offline_msg.timestamp, Rs))); - _ -> Ls - end; -pop_offline_messages(Ls, LUser, LServer, odbc) -> - case odbc_queries:get_and_del_spool_msg_t(LServer, LUser) of - {atomic, {selected, Rs}} -> - Ls ++ - lists:flatmap(fun ({_, XML}) -> - case fxml_stream:parse_element(XML) of - {error, _Reason} -> - []; - El -> - case offline_msg_to_route(LServer, El) of - error -> - []; - RouteMsg -> - [RouteMsg] - end - end + Mod = gen_mod:db_mod(LServer, ?MODULE), + case Mod:pop_messages(LUser, LServer) of + {ok, Rs} -> + TS = p1_time_compat:timestamp(), + Ls ++ + lists:map(fun (R) -> + offline_msg_to_route(LServer, R) end, - Rs); - _ -> Ls - end; -pop_offline_messages(Ls, LUser, LServer, riak) -> - case ejabberd_riak:get_by_index(offline_msg, offline_msg_schema(), - <<"us">>, {LUser, LServer}) of - {ok, Rs} -> - try - lists:foreach( - fun(#offline_msg{timestamp = T}) -> - ok = ejabberd_riak:delete(offline_msg, T) - end, Rs), - TS = p1_time_compat:timestamp(), - Ls ++ lists:map( - fun (R) -> - offline_msg_to_route(LServer, R) - end, - lists:filter( - fun(R) -> - case R#offline_msg.expire of - never -> true; - TimeStamp -> TS < TimeStamp - end - end, - lists:keysort(#offline_msg.timestamp, Rs))) - catch _:{badmatch, _} -> - Ls - end; + lists:filter( + fun(#offline_msg{packet = Pkt} = R) -> + #xmlel{children = Els} = Pkt, + Expire = case R#offline_msg.expire of + undefined -> + find_x_expire(TS, Els); + Exp -> + Exp + end, + case Expire of + never -> true; + TimeStamp -> TS < TimeStamp + end + end, Rs)); _ -> Ls end. remove_expired_messages(Server) -> LServer = jid:nameprep(Server), - remove_expired_messages(LServer, - gen_mod:db_type(LServer, ?MODULE)). - -remove_expired_messages(_LServer, mnesia) -> - TimeStamp = p1_time_compat:timestamp(), - F = fun () -> - mnesia:write_lock_table(offline_msg), - mnesia:foldl(fun (Rec, _Acc) -> - case Rec#offline_msg.expire of - never -> ok; - TS -> - if TS < TimeStamp -> - mnesia:delete_object(Rec); - true -> ok - end - end - end, - ok, offline_msg) - end, - mnesia:transaction(F); -remove_expired_messages(_LServer, odbc) -> {atomic, ok}; -remove_expired_messages(_LServer, riak) -> {atomic, ok}. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:remove_expired_messages(LServer). remove_old_messages(Days, Server) -> LServer = jid:nameprep(Server), - remove_old_messages(Days, LServer, - gen_mod:db_type(LServer, ?MODULE)). - -remove_old_messages(Days, _LServer, mnesia) -> - S = p1_time_compat:system_time(seconds) - 60 * 60 * 24 * Days, - MegaSecs1 = S div 1000000, - Secs1 = S rem 1000000, - TimeStamp = {MegaSecs1, Secs1, 0}, - F = fun () -> - mnesia:write_lock_table(offline_msg), - mnesia:foldl(fun (#offline_msg{timestamp = TS} = Rec, - _Acc) - when TS < TimeStamp -> - mnesia:delete_object(Rec); - (_Rec, _Acc) -> ok - end, - ok, offline_msg) - end, - mnesia:transaction(F); - -remove_old_messages(Days, LServer, odbc) -> - case catch ejabberd_odbc:sql_query( - LServer, - [<<"DELETE FROM spool" - " WHERE created_at < " - "DATE_SUB(CURDATE(), INTERVAL ">>, - integer_to_list(Days), <<" DAY);">>]) of - {updated, N} -> - ?INFO_MSG("~p message(s) deleted from offline spool", [N]); - _Error -> - ?ERROR_MSG("Cannot delete message in offline spool: ~p", [_Error]) - end, - {atomic, ok}; -remove_old_messages(_Days, _LServer, riak) -> - {atomic, ok}. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:remove_old_messages(Days, LServer). remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - remove_user(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). - -remove_user(LUser, LServer, mnesia) -> - US = {LUser, LServer}, - F = fun () -> mnesia:delete({offline_msg, US}) end, - mnesia:transaction(F); -remove_user(LUser, LServer, odbc) -> - odbc_queries:del_spool_msg(LServer, LUser); -remove_user(LUser, LServer, riak) -> - {atomic, ejabberd_riak:delete_by_index(offline_msg, - <<"us">>, {LUser, LServer})}. - -jid_to_binary(#jid{user = U, server = S, resource = R, - luser = LU, lserver = LS, lresource = LR}) -> - #jid{user = iolist_to_binary(U), - server = iolist_to_binary(S), - resource = iolist_to_binary(R), - luser = iolist_to_binary(LU), - lserver = iolist_to_binary(LS), - lresource = iolist_to_binary(LR)}. - -update_table() -> - Fields = record_info(fields, offline_msg), - case mnesia:table_info(offline_msg, attributes) of - Fields -> - ejabberd_config:convert_table_to_binary( - offline_msg, Fields, bag, - fun(#offline_msg{us = {U, _}}) -> U end, - fun(#offline_msg{us = {U, S}, - from = From, - to = To, - packet = El} = R) -> - R#offline_msg{us = {iolist_to_binary(U), - iolist_to_binary(S)}, - from = jid_to_binary(From), - to = jid_to_binary(To), - packet = fxml:to_xmlel(El)} - end); - _ -> - ?INFO_MSG("Recreating offline_msg table", []), - mnesia:transform_table(offline_msg, ignore, Fields) - end. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:remove_user(LUser, LServer). %% Helper functions: @@ -880,255 +666,71 @@ webadmin_page(_, Host, webadmin_page(Acc, _, _) -> Acc. get_offline_els(LUser, LServer) -> - get_offline_els(LUser, LServer, gen_mod:db_type(LServer, ?MODULE)). - -get_offline_els(LUser, LServer, DBType) - when DBType == mnesia; DBType == riak -> - Msgs = read_all_msgs(LUser, LServer, DBType), + Mod = gen_mod:db_mod(LServer, ?MODULE), + Hdrs = Mod:read_message_headers(LUser, LServer), lists:map( - fun(Msg) -> - {route, From, To, Packet} = offline_msg_to_route(LServer, Msg), - jlib:replace_from_to(From, To, Packet) - end, Msgs); -get_offline_els(LUser, LServer, odbc) -> - case catch ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(xml)s from spool where " - "username=%(LUser)s order by seq")) of - {selected, Rs} -> - lists:flatmap( - fun({XML}) -> - case fxml_stream:parse_element(XML) of - #xmlel{} = El -> - case offline_msg_to_route(LServer, El) of - {route, _, _, NewEl} -> - [NewEl]; - error -> - [] - end; - _ -> - [] - end - end, Rs); - _ -> - [] - end. + fun({_Seq, From, To, Packet}) -> + jlib:replace_from_to(From, To, Packet) + end, Hdrs). offline_msg_to_route(LServer, #offline_msg{} = R) -> - {route, R#offline_msg.from, R#offline_msg.to, - jlib:add_delay_info(R#offline_msg.packet, LServer, R#offline_msg.timestamp, - <<"Offline Storage">>)}; -offline_msg_to_route(_LServer, #xmlel{} = El) -> - To = jid:from_string(fxml:get_tag_attr_s(<<"to">>, El)), - From = jid:from_string(fxml:get_tag_attr_s(<<"from">>, El)), - if (To /= error) and (From /= error) -> - {route, From, To, El}; - true -> - error - end. - -binary_to_timestamp(TS) -> - case catch jlib:binary_to_integer(TS) of - Int when is_integer(Int) -> - Secs = Int div 1000000, - USec = Int rem 1000000, - MSec = Secs div 1000000, - Sec = Secs rem 1000000, - {MSec, Sec, USec}; - _ -> - undefined - end. - -timestamp_to_binary({MS, S, US}) -> - format_timestamp(integer_to_list((MS * 1000000 + S) * 1000000 + US)). - -format_timestamp(TS) -> - iolist_to_binary(io_lib:format("~20..0s", [TS])). - -offline_msg_to_header(#offline_msg{from = From, timestamp = Int} = Msg) -> - TS = timestamp_to_binary(Int), - From_s = jid:to_string(From), - {<>, From_s, Msg}. + El = case R#offline_msg.timestamp of + undefined -> + R#offline_msg.packet; + TS -> + jlib:add_delay_info(R#offline_msg.packet, LServer, TS, + <<"Offline Storage">>) + end, + {route, R#offline_msg.from, R#offline_msg.to, El}. read_message_headers(LUser, LServer) -> - DBType = gen_mod:db_type(LServer, ?MODULE), - read_message_headers(LUser, LServer, DBType). + Mod = gen_mod:db_mod(LServer, ?MODULE), + lists:map( + fun({Seq, From, To, El}) -> + Node = integer_to_binary(Seq), + {Node, From, To, El} + end, Mod:read_message_headers(LUser, LServer)). -read_message_headers(LUser, LServer, mnesia) -> - Msgs = mnesia:dirty_read({offline_msg, {LUser, LServer}}), - Hdrs = lists:map(fun offline_msg_to_header/1, Msgs), - lists:keysort(1, Hdrs); -read_message_headers(LUser, LServer, riak) -> - case ejabberd_riak:get_by_index( - offline_msg, offline_msg_schema(), - <<"us">>, {LUser, LServer}) of - {ok, Rs} -> - Hdrs = lists:map(fun offline_msg_to_header/1, Rs), - lists:keysort(1, Hdrs); - _Err -> - [] - end; -read_message_headers(LUser, LServer, odbc) -> - Username = ejabberd_odbc:escape(LUser), - case catch ejabberd_odbc:sql_query( - LServer, [<<"select xml, seq from spool where username ='">>, - Username, <<"' order by seq;">>]) of - {selected, [<<"xml">>, <<"seq">>], Rows} -> - Hdrs = lists:flatmap( - fun([XML, Seq]) -> - try - #xmlel{} = El = fxml_stream:parse_element(XML), - From = fxml:get_tag_attr_s(<<"from">>, El), - #jid{} = jid:from_string(From), - TS = format_timestamp(Seq), - [{<>, From, El}] - catch _:_ -> [] - end - end, Rows), - lists:keysort(1, Hdrs); - _Err -> - [] - end. - -read_message(_From, To, TS, mnesia) -> - {U, S, _} = jid:tolower(To), - case mnesia:dirty_match_object( - offline_msg, #offline_msg{us = {U, S}, timestamp = TS, _ = '_'}) of - [Msg|_] -> - {ok, Msg}; - _ -> - error - end; -read_message(_From, _To, TS, riak) -> - case ejabberd_riak:get(offline_msg, offline_msg_schema(), TS) of - {ok, Msg} -> - {ok, Msg}; - _ -> - error - end; -read_message(_From, To, Seq, odbc) -> - {LUser, LServer, _} = jid:tolower(To), - Username = ejabberd_odbc:escape(LUser), - SSeq = ejabberd_odbc:escape(Seq), - case ejabberd_odbc:sql_query( - LServer, - [<<"select xml from spool where username='">>, Username, - <<"' and seq='">>, SSeq, <<"';">>]) of - {selected, [<<"xml">>], [[RawXML]|_]} -> - case fxml_stream:parse_element(RawXML) of - #xmlel{} = El -> {ok, El}; - {error, _} -> error - end; - _ -> - error - end. - -remove_message(_From, To, TS, mnesia) -> - {U, S, _} = jid:tolower(To), - Msgs = mnesia:dirty_match_object( - offline_msg, #offline_msg{us = {U, S}, timestamp = TS, _ = '_'}), - lists:foreach( - fun(Msg) -> - mnesia:dirty_delete_object(Msg) - end, Msgs); -remove_message(_From, _To, TS, riak) -> - ejabberd_riak:delete(offline_msg, TS), - ok; -remove_message(_From, To, Seq, odbc) -> - {LUser, LServer, _} = jid:tolower(To), - Username = ejabberd_odbc:escape(LUser), - SSeq = ejabberd_odbc:escape(Seq), - ejabberd_odbc:sql_query( - LServer, - [<<"delete from spool where username='">>, Username, - <<"' and seq='">>, SSeq, <<"';">>]), - ok. - -read_all_msgs(LUser, LServer, mnesia) -> - US = {LUser, LServer}, - lists:keysort(#offline_msg.timestamp, - mnesia:dirty_read({offline_msg, US})); -read_all_msgs(LUser, LServer, riak) -> - case ejabberd_riak:get_by_index( - offline_msg, offline_msg_schema(), - <<"us">>, {LUser, LServer}) of - {ok, Rs} -> - lists:keysort(#offline_msg.timestamp, Rs); - _Err -> - [] - end; -read_all_msgs(LUser, LServer, odbc) -> - case catch ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(xml)s from spool where " - "username=%(LUser)s order by seq")) of - {selected, Rs} -> - lists:flatmap( - fun({XML}) -> - case fxml_stream:parse_element(XML) of - {error, _Reason} -> []; - El -> [El] - end - end, - Rs); - _ -> [] - end. - -format_user_queue(Msgs, DBType) when DBType == mnesia; DBType == riak -> - lists:map(fun (#offline_msg{timestamp = TimeStamp, - from = From, to = To, - packet = - #xmlel{name = Name, attrs = Attrs, - children = Els}} = - Msg) -> - ID = jlib:encode_base64((term_to_binary(Msg))), - {{Year, Month, Day}, {Hour, Minute, Second}} = - calendar:now_to_local_time(TimeStamp), - Time = - iolist_to_binary(io_lib:format("~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", - [Year, Month, Day, - Hour, Minute, - Second])), - SFrom = jid:to_string(From), - STo = jid:to_string(To), - Attrs2 = jlib:replace_from_to_attrs(SFrom, STo, Attrs), - Packet = #xmlel{name = Name, attrs = Attrs2, - children = Els}, - FPacket = ejabberd_web_admin:pretty_print_xml(Packet), - ?XE(<<"tr">>, - [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], - [?INPUT(<<"checkbox">>, <<"selected">>, ID)]), - ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], Time), - ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], SFrom), - ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], STo), - ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], - [?XC(<<"pre">>, FPacket)])]) - end, - Msgs); -format_user_queue(Msgs, odbc) -> - lists:map(fun (#xmlel{} = Msg) -> - ID = jlib:encode_base64((term_to_binary(Msg))), - Packet = Msg, - FPacket = ejabberd_web_admin:pretty_print_xml(Packet), - ?XE(<<"tr">>, - [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], - [?INPUT(<<"checkbox">>, <<"selected">>, ID)]), - ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], - [?XC(<<"pre">>, FPacket)])]) - end, - Msgs). +format_user_queue(Hdrs) -> + lists:map( + fun({Seq, From, To, El}) -> + ID = integer_to_binary(Seq), + FPacket = ejabberd_web_admin:pretty_print_xml(El), + SFrom = jid:to_string(From), + STo = jid:to_string(To), + Stamp = fxml:get_path_s(El, [{elem, <<"delay">>}, + {attr, <<"stamp">>}]), + Time = case jlib:datetime_string_to_timestamp(Stamp) of + {_, _, _} = Now -> + {{Year, Month, Day}, {Hour, Minute, Second}} = + calendar:now_to_local_time(Now), + iolist_to_binary( + io_lib:format( + "~w-~.2.0w-~.2.0w ~.2.0w:~.2.0w:~.2.0w", + [Year, Month, Day, Hour, Minute, + Second])); + _ -> + <<"">> + end, + ?XE(<<"tr">>, + [?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], + [?INPUT(<<"checkbox">>, <<"selected">>, ID)]), + ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], Time), + ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], SFrom), + ?XAC(<<"td">>, [{<<"class">>, <<"valign">>}], STo), + ?XAE(<<"td">>, [{<<"class">>, <<"valign">>}], + [?XC(<<"pre">>, FPacket)])]) + end, Hdrs). user_queue(User, Server, Query, Lang) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), US = {LUser, LServer}, - DBType = gen_mod:db_type(LServer, ?MODULE), - Res = user_queue_parse_query(LUser, LServer, Query, - DBType), - MsgsAll = read_all_msgs(LUser, LServer, DBType), - Msgs = get_messages_subset(US, Server, MsgsAll, - DBType), - FMsgs = format_user_queue(Msgs, DBType), + Mod = gen_mod:db_mod(LServer, ?MODULE), + Res = user_queue_parse_query(LUser, LServer, Query), + HdrsAll = Mod:read_message_headers(LUser, LServer), + Hdrs = get_messages_subset(US, Server, HdrsAll), + FMsgs = format_user_queue(Hdrs), [?XC(<<"h1">>, list_to_binary(io_lib:format(?T(<<"~s's Offline Messages Queue">>), [us_to_list(US)])))] @@ -1158,96 +760,24 @@ user_queue(User, Server, Query, Lang) -> ?INPUTT(<<"submit">>, <<"delete">>, <<"Delete Selected">>)])]. -user_queue_parse_query(LUser, LServer, Query, mnesia) -> - US = {LUser, LServer}, +user_queue_parse_query(LUser, LServer, Query) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), case lists:keysearch(<<"delete">>, 1, Query) of - {value, _} -> - Msgs = lists:keysort(#offline_msg.timestamp, - mnesia:dirty_read({offline_msg, US})), - F = fun () -> - lists:foreach(fun (Msg) -> - ID = - jlib:encode_base64((term_to_binary(Msg))), - case lists:member({<<"selected">>, - ID}, - Query) - of - true -> mnesia:delete_object(Msg); - false -> ok - end - end, - Msgs) - end, - mnesia:transaction(F), - ok; - false -> nothing - end; -user_queue_parse_query(LUser, LServer, Query, riak) -> - case lists:keysearch(<<"delete">>, 1, Query) of - {value, _} -> - Msgs = read_all_msgs(LUser, LServer, riak), - lists:foreach( - fun (Msg) -> - ID = jlib:encode_base64((term_to_binary(Msg))), - case lists:member({<<"selected">>, ID}, Query) of - true -> - ejabberd_riak:delete(offline_msg, - Msg#offline_msg.timestamp); - false -> - ok - end - end, - Msgs), - ok; - false -> - nothing - end; -user_queue_parse_query(LUser, LServer, Query, odbc) -> - Username = ejabberd_odbc:escape(LUser), - case lists:keysearch(<<"delete">>, 1, Query) of - {value, _} -> - Msgs = case catch ejabberd_odbc:sql_query(LServer, - [<<"select xml, seq from spool where username='">>, - Username, - <<"' order by seq;">>]) - of - {selected, [<<"xml">>, <<"seq">>], Rs} -> - lists:flatmap(fun ([XML, Seq]) -> - case fxml_stream:parse_element(XML) - of - {error, _Reason} -> []; - El -> [{El, Seq}] - end - end, - Rs); - _ -> [] - end, - F = fun () -> - lists:foreach(fun ({Msg, Seq}) -> - ID = - jlib:encode_base64((term_to_binary(Msg))), - case lists:member({<<"selected">>, - ID}, - Query) - of - true -> - SSeq = - ejabberd_odbc:escape(Seq), - catch - ejabberd_odbc:sql_query(LServer, - [<<"delete from spool where username='">>, - Username, - <<"' and seq='">>, - SSeq, - <<"';">>]); - false -> ok - end - end, - Msgs) - end, - mnesia:transaction(F), - ok; - false -> nothing + {value, _} -> + case lists:keyfind(<<"selected">>, 1, Query) of + {_, Seq} -> + case catch binary_to_integer(Seq) of + I when is_integer(I), I>=0 -> + Mod:remove_message(LUser, LServer, I), + ok; + _ -> + nothing + end; + false -> + nothing + end; + _ -> + nothing end. us_to_list({User, Server}) -> @@ -1256,7 +786,7 @@ us_to_list({User, Server}) -> get_queue_length(LUser, LServer) -> count_offline_messages(LUser, LServer). -get_messages_subset(User, Host, MsgsAll, DBType) -> +get_messages_subset(User, Host, MsgsAll) -> Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages, fun(A) when is_atom(A) -> A end, max_user_offline_messages), @@ -1267,33 +797,20 @@ get_messages_subset(User, Host, MsgsAll, DBType) -> _ -> 100 end, Length = length(MsgsAll), - get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll, - DBType). + get_messages_subset2(MaxOfflineMsgs, Length, MsgsAll). -get_messages_subset2(Max, Length, MsgsAll, _DBType) - when Length =< Max * 2 -> +get_messages_subset2(Max, Length, MsgsAll) when Length =< Max * 2 -> MsgsAll; -get_messages_subset2(Max, Length, MsgsAll, DBType) - when DBType == mnesia; DBType == riak -> +get_messages_subset2(Max, Length, MsgsAll) -> FirstN = Max, {MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll), MsgsLastN = lists:nthtail(Length - FirstN - FirstN, Msgs2), NoJID = jid:make(<<"...">>, <<"...">>, <<"">>), - IntermediateMsg = #offline_msg{timestamp = p1_time_compat:timestamp(), - from = NoJID, to = NoJID, - packet = - #xmlel{name = <<"...">>, attrs = [], - children = []}}, - MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN; -get_messages_subset2(Max, Length, MsgsAll, odbc) -> - FirstN = Max, - {MsgsFirstN, Msgs2} = lists:split(FirstN, MsgsAll), - MsgsLastN = lists:nthtail(Length - FirstN - FirstN, - Msgs2), + Seq = <<"0">>, IntermediateMsg = #xmlel{name = <<"...">>, attrs = [], children = []}, - MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN. + MsgsFirstN ++ [{Seq, NoJID, NoJID, IntermediateMsg}] ++ MsgsLastN. webadmin_user(Acc, User, Server, Lang) -> QueueLen = count_offline_messages(jid:nodeprep(User), @@ -1310,25 +827,8 @@ webadmin_user(Acc, User, Server, Lang) -> delete_all_msgs(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - delete_all_msgs(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). - -delete_all_msgs(LUser, LServer, mnesia) -> - US = {LUser, LServer}, - F = fun () -> - mnesia:write_lock_table(offline_msg), - lists:foreach(fun (Msg) -> mnesia:delete_object(Msg) - end, - mnesia:dirty_read({offline_msg, US})) - end, - mnesia:transaction(F); -delete_all_msgs(LUser, LServer, riak) -> - Res = ejabberd_riak:delete_by_index(offline_msg, - <<"us">>, {LUser, LServer}), - {atomic, Res}; -delete_all_msgs(LUser, LServer, odbc) -> - odbc_queries:del_spool_msg(LServer, LUser), - {atomic, ok}. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:remove_all_messages(LUser, LServer). webadmin_user_parse_query(_, <<"removealloffline">>, User, Server, _Query) -> @@ -1350,112 +850,20 @@ webadmin_user_parse_query(Acc, _Action, _User, _Server, count_offline_messages(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - DBType = gen_mod:db_type(LServer, ?MODULE), - count_offline_messages(LUser, LServer, DBType). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:count_messages(LUser, LServer). -count_offline_messages(LUser, LServer, mnesia) -> - US = {LUser, LServer}, - F = fun () -> - count_mnesia_records(US) - end, - case catch mnesia:async_dirty(F) of - I when is_integer(I) -> I; - _ -> 0 - end; -count_offline_messages(LUser, LServer, odbc) -> - case catch ejabberd_odbc:sql_query( - LServer, - ?SQL("select @(count(*))d from spool " - "where username=%(LUser)s")) of - {selected, [{Res}]} -> - Res; - _ -> 0 - end; -count_offline_messages(LUser, LServer, riak) -> - case ejabberd_riak:count_by_index( - offline_msg, <<"us">>, {LUser, LServer}) of - {ok, Res} -> - Res; - _ -> - 0 - end. - -%% Return the number of records matching a given match expression. -%% This function is intended to be used inside a Mnesia transaction. -%% The count has been written to use the fewest possible memory by -%% getting the record by small increment and by using continuation. --define(BATCHSIZE, 100). - -count_mnesia_records(US) -> - MatchExpression = #offline_msg{us = US, _ = '_'}, - case mnesia:select(offline_msg, [{MatchExpression, [], [[]]}], - ?BATCHSIZE, read) of - {Result, Cont} -> - Count = length(Result), - count_records_cont(Cont, Count); - '$end_of_table' -> - 0 - end. - -count_records_cont(Cont, Count) -> - case mnesia:select(Cont) of - {Result, Cont} -> - NewCount = Count + length(Result), - count_records_cont(Cont, NewCount); - '$end_of_table' -> - Count - end. - -offline_msg_schema() -> - {record_info(fields, offline_msg), #offline_msg{}}. - -export(_Server) -> - [{offline_msg, - fun(Host, #offline_msg{us = {LUser, LServer}, - timestamp = TimeStamp, from = From, to = To, - packet = Packet}) - when LServer == Host -> - Username = ejabberd_odbc:escape(LUser), - Packet1 = jlib:replace_from_to(From, To, Packet), - Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp, - <<"Offline Storage">>), - XML = ejabberd_odbc:escape(fxml:element_to_binary(Packet2)), - [[<<"delete from spool where username='">>, Username, <<"';">>], - [<<"insert into spool(username, xml) values ('">>, - Username, <<"', '">>, XML, <<"');">>]]; - (_Host, _R) -> - [] - end}]. +export(LServer) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:export(LServer). import(LServer) -> - [{<<"select username, xml from spool;">>, - fun([LUser, XML]) -> - El = #xmlel{} = fxml_stream:parse_element(XML), - From = #jid{} = jid:from_string( - fxml:get_attr_s(<<"from">>, El#xmlel.attrs)), - To = #jid{} = jid:from_string( - fxml:get_attr_s(<<"to">>, El#xmlel.attrs)), - Stamp = fxml:get_path_s(El, [{elem, <<"delay">>}, - {attr, <<"stamp">>}]), - TS = case jlib:datetime_string_to_timestamp(Stamp) of - {_, _, _} = Now -> - Now; - undefined -> - p1_time_compat:timestamp() - end, - Expire = find_x_expire(TS, El#xmlel.children), - #offline_msg{us = {LUser, LServer}, - from = From, to = To, - timestamp = TS, expire = Expire} - end}]. + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:import(LServer). -import(_LServer, mnesia, #offline_msg{} = Msg) -> - mnesia:dirty_write(Msg); -import(_LServer, riak, #offline_msg{us = US, timestamp = TS} = M) -> - ejabberd_riak:put(M, offline_msg_schema(), - [{i, TS}, {'2i', [{<<"us">>, US}]}]); -import(_, _, _) -> - pass. +import(LServer, DBType, Data) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:import(LServer, Data). mod_opt_type(access_max_user_messages) -> fun (A) -> A end; diff --git a/src/mod_offline_mnesia.erl b/src/mod_offline_mnesia.erl new file mode 100644 index 000000000..6a1d9e309 --- /dev/null +++ b/src/mod_offline_mnesia.erl @@ -0,0 +1,232 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 15 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_offline_mnesia). + +-behaviour(mod_offline). + +-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1, + remove_old_messages/2, remove_user/2, read_message_headers/2, + read_message/3, remove_message/3, read_all_messages/2, + remove_all_messages/2, count_messages/2, import/2]). + +-include("jlib.hrl"). +-include("mod_offline.hrl"). +-include("logger.hrl"). + +-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + mnesia:create_table(offline_msg, + [{disc_only_copies, [node()]}, {type, bag}, + {attributes, record_info(fields, offline_msg)}]), + update_table(). + +store_messages(_Host, US, Msgs, Len, MaxOfflineMsgs) -> + F = fun () -> + Count = if MaxOfflineMsgs =/= infinity -> + Len + count_mnesia_records(US); + true -> 0 + end, + if Count > MaxOfflineMsgs -> discard; + true -> + if Len >= (?OFFLINE_TABLE_LOCK_THRESHOLD) -> + mnesia:write_lock_table(offline_msg); + true -> ok + end, + lists:foreach(fun (M) -> mnesia:write(M) end, Msgs) + end + end, + mnesia:transaction(F). + +pop_messages(LUser, LServer) -> + US = {LUser, LServer}, + F = fun () -> + Rs = mnesia:wread({offline_msg, US}), + mnesia:delete({offline_msg, US}), + Rs + end, + case mnesia:transaction(F) of + {atomic, L} -> + {ok, lists:keysort(#offline_msg.timestamp, L)}; + {aborted, Reason} -> + {error, Reason} + end. + +remove_expired_messages(_LServer) -> + TimeStamp = p1_time_compat:timestamp(), + F = fun () -> + mnesia:write_lock_table(offline_msg), + mnesia:foldl(fun (Rec, _Acc) -> + case Rec#offline_msg.expire of + never -> ok; + TS -> + if TS < TimeStamp -> + mnesia:delete_object(Rec); + true -> ok + end + end + end, + ok, offline_msg) + end, + mnesia:transaction(F). + +remove_old_messages(Days, _LServer) -> + S = p1_time_compat:system_time(seconds) - 60 * 60 * 24 * Days, + MegaSecs1 = S div 1000000, + Secs1 = S rem 1000000, + TimeStamp = {MegaSecs1, Secs1, 0}, + F = fun () -> + mnesia:write_lock_table(offline_msg), + mnesia:foldl(fun (#offline_msg{timestamp = TS} = Rec, + _Acc) + when TS < TimeStamp -> + mnesia:delete_object(Rec); + (_Rec, _Acc) -> ok + end, + ok, offline_msg) + end, + mnesia:transaction(F). + +remove_user(LUser, LServer) -> + US = {LUser, LServer}, + F = fun () -> mnesia:delete({offline_msg, US}) end, + mnesia:transaction(F). + +read_message_headers(LUser, LServer) -> + Msgs = mnesia:dirty_read({offline_msg, {LUser, LServer}}), + Hdrs = lists:map( + fun(#offline_msg{from = From, to = To, packet = Pkt, + timestamp = TS}) -> + Seq = now_to_integer(TS), + NewPkt = jlib:add_delay_info(Pkt, LServer, TS, + <<"Offline Storage">>), + {Seq, From, To, NewPkt} + end, Msgs), + lists:keysort(1, Hdrs). + +read_message(LUser, LServer, I) -> + US = {LUser, LServer}, + TS = integer_to_now(I), + case mnesia:dirty_match_object( + offline_msg, #offline_msg{us = US, timestamp = TS, _ = '_'}) of + [Msg|_] -> + {ok, Msg}; + _ -> + error + end. + +remove_message(LUser, LServer, I) -> + US = {LUser, LServer}, + TS = integer_to_now(I), + Msgs = mnesia:dirty_match_object( + offline_msg, #offline_msg{us = US, timestamp = TS, _ = '_'}), + lists:foreach( + fun(Msg) -> + mnesia:dirty_delete_object(Msg) + end, Msgs). + +read_all_messages(LUser, LServer) -> + US = {LUser, LServer}, + lists:keysort(#offline_msg.timestamp, + mnesia:dirty_read({offline_msg, US})). + +remove_all_messages(LUser, LServer) -> + US = {LUser, LServer}, + F = fun () -> + mnesia:write_lock_table(offline_msg), + lists:foreach(fun (Msg) -> mnesia:delete_object(Msg) end, + mnesia:dirty_read({offline_msg, US})) + end, + mnesia:transaction(F). + +count_messages(LUser, LServer) -> + US = {LUser, LServer}, + F = fun () -> + count_mnesia_records(US) + end, + case catch mnesia:async_dirty(F) of + I when is_integer(I) -> I; + _ -> 0 + end. + +import(_LServer, #offline_msg{} = Msg) -> + mnesia:dirty_write(Msg). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +%% Return the number of records matching a given match expression. +%% This function is intended to be used inside a Mnesia transaction. +%% The count has been written to use the fewest possible memory by +%% getting the record by small increment and by using continuation. +-define(BATCHSIZE, 100). + +count_mnesia_records(US) -> + MatchExpression = #offline_msg{us = US, _ = '_'}, + case mnesia:select(offline_msg, [{MatchExpression, [], [[]]}], + ?BATCHSIZE, read) of + {Result, Cont} -> + Count = length(Result), + count_records_cont(Cont, Count); + '$end_of_table' -> + 0 + end. + +count_records_cont(Cont, Count) -> + case mnesia:select(Cont) of + {Result, Cont} -> + NewCount = Count + length(Result), + count_records_cont(Cont, NewCount); + '$end_of_table' -> + Count + end. + +jid_to_binary(#jid{user = U, server = S, resource = R, + luser = LU, lserver = LS, lresource = LR}) -> + #jid{user = iolist_to_binary(U), + server = iolist_to_binary(S), + resource = iolist_to_binary(R), + luser = iolist_to_binary(LU), + lserver = iolist_to_binary(LS), + lresource = iolist_to_binary(LR)}. + +now_to_integer({MS, S, US}) -> + (MS * 1000000 + S) * 1000000 + US. + +integer_to_now(Int) -> + Secs = Int div 1000000, + USec = Int rem 1000000, + MSec = Secs div 1000000, + Sec = Secs rem 1000000, + {MSec, Sec, USec}. + +update_table() -> + Fields = record_info(fields, offline_msg), + case mnesia:table_info(offline_msg, attributes) of + Fields -> + ejabberd_config:convert_table_to_binary( + offline_msg, Fields, bag, + fun(#offline_msg{us = {U, _}}) -> U end, + fun(#offline_msg{us = {U, S}, + from = From, + to = To, + packet = El} = R) -> + R#offline_msg{us = {iolist_to_binary(U), + iolist_to_binary(S)}, + from = jid_to_binary(From), + to = jid_to_binary(To), + packet = fxml:to_xmlel(El)} + end); + _ -> + ?INFO_MSG("Recreating offline_msg table", []), + mnesia:transform_table(offline_msg, ignore, Fields) + end. diff --git a/src/mod_offline_riak.erl b/src/mod_offline_riak.erl new file mode 100644 index 000000000..217e8f828 --- /dev/null +++ b/src/mod_offline_riak.erl @@ -0,0 +1,153 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 15 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_offline_riak). + +-behaviour(mod_offline). + +-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1, + remove_old_messages/2, remove_user/2, read_message_headers/2, + read_message/3, remove_message/3, read_all_messages/2, + remove_all_messages/2, count_messages/2, import/2]). + +-include("jlib.hrl"). +-include("mod_offline.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +store_messages(Host, {User, _}, Msgs, Len, MaxOfflineMsgs) -> + Count = if MaxOfflineMsgs =/= infinity -> + Len + count_messages(User, Host); + true -> 0 + end, + if + Count > MaxOfflineMsgs -> + {atomic, discard}; + true -> + try + lists:foreach( + fun(#offline_msg{us = US, + timestamp = TS} = M) -> + ok = ejabberd_riak:put( + M, offline_msg_schema(), + [{i, TS}, {'2i', [{<<"us">>, US}]}]) + end, Msgs), + {atomic, ok} + catch _:{badmatch, Err} -> + {atomic, Err} + end + end. + +pop_messages(LUser, LServer) -> + case ejabberd_riak:get_by_index(offline_msg, offline_msg_schema(), + <<"us">>, {LUser, LServer}) of + {ok, Rs} -> + try + lists:foreach( + fun(#offline_msg{timestamp = T}) -> + ok = ejabberd_riak:delete(offline_msg, T) + end, Rs), + {ok, lists:keysort(#offline_msg.timestamp, Rs)} + catch _:{badmatch, Err} -> + Err + end; + Err -> + Err + end. + +remove_expired_messages(_LServer) -> + %% TODO + {atomic, ok}. + +remove_old_messages(_Days, _LServer) -> + %% TODO + {atomic, ok}. + +remove_user(LUser, LServer) -> + {atomic, ejabberd_riak:delete_by_index(offline_msg, + <<"us">>, {LUser, LServer})}. + +read_message_headers(LUser, LServer) -> + case ejabberd_riak:get_by_index( + offline_msg, offline_msg_schema(), + <<"us">>, {LUser, LServer}) of + {ok, Rs} -> + Hdrs = lists:map( + fun(#offline_msg{from = From, to = To, packet = Pkt, + timestamp = TS}) -> + Seq = now_to_integer(TS), + NewPkt = jlib:add_delay_info( + Pkt, LServer, TS, <<"Offline Storage">>), + {Seq, From, To, NewPkt} + end, Rs), + lists:keysort(1, Hdrs); + _Err -> + [] + end. + +read_message(_LUser, _LServer, I) -> + TS = integer_to_now(I), + case ejabberd_riak:get(offline_msg, offline_msg_schema(), TS) of + {ok, Msg} -> + {ok, Msg}; + _ -> + error + end. + +remove_message(_LUser, _LServer, I) -> + TS = integer_to_now(I), + ejabberd_riak:delete(offline_msg, TS), + ok. + +read_all_messages(LUser, LServer) -> + case ejabberd_riak:get_by_index( + offline_msg, offline_msg_schema(), + <<"us">>, {LUser, LServer}) of + {ok, Rs} -> + lists:keysort(#offline_msg.timestamp, Rs); + _Err -> + [] + end. + +remove_all_messages(LUser, LServer) -> + Res = ejabberd_riak:delete_by_index(offline_msg, + <<"us">>, {LUser, LServer}), + {atomic, Res}. + +count_messages(LUser, LServer) -> + case ejabberd_riak:count_by_index( + offline_msg, <<"us">>, {LUser, LServer}) of + {ok, Res} -> + Res; + _ -> + 0 + end. + +import(_LServer, #offline_msg{us = US, timestamp = TS} = M) -> + ejabberd_riak:put(M, offline_msg_schema(), + [{i, TS}, {'2i', [{<<"us">>, US}]}]). + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +offline_msg_schema() -> + {record_info(fields, offline_msg), #offline_msg{}}. + +now_to_integer({MS, S, US}) -> + (MS * 1000000 + S) * 1000000 + US. + +integer_to_now(Int) -> + Secs = Int div 1000000, + USec = Int rem 1000000, + MSec = Secs div 1000000, + Sec = Secs rem 1000000, + {MSec, Sec, USec}. diff --git a/src/mod_offline_sql.erl b/src/mod_offline_sql.erl new file mode 100644 index 000000000..37b90163d --- /dev/null +++ b/src/mod_offline_sql.erl @@ -0,0 +1,252 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 15 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_offline_sql). + +-compile([{parse_transform, ejabberd_sql_pt}]). + +-behaviour(mod_offline). + +-export([init/2, store_messages/5, pop_messages/2, remove_expired_messages/1, + remove_old_messages/2, remove_user/2, read_message_headers/2, + read_message/3, remove_message/3, read_all_messages/2, + remove_all_messages/2, count_messages/2, import/1, import/2, + export/1]). + +-include("jlib.hrl"). +-include("mod_offline.hrl"). +-include("logger.hrl"). +-include("ejabberd_sql_pt.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +store_messages(Host, {User, _Server}, Msgs, Len, MaxOfflineMsgs) -> + Count = if MaxOfflineMsgs =/= infinity -> + Len + count_messages(User, Host); + true -> 0 + end, + if Count > MaxOfflineMsgs -> {atomic, discard}; + true -> + Query = lists:map( + fun(M) -> + Username = + ejabberd_odbc:escape((M#offline_msg.to)#jid.luser), + From = M#offline_msg.from, + To = M#offline_msg.to, + Packet = + jlib:replace_from_to(From, To, + M#offline_msg.packet), + NewPacket = + jlib:add_delay_info(Packet, Host, + M#offline_msg.timestamp, + <<"Offline Storage">>), + XML = + ejabberd_odbc:escape(fxml:element_to_binary(NewPacket)), + odbc_queries:add_spool_sql(Username, XML) + end, + Msgs), + odbc_queries:add_spool(Host, Query) + end. + +pop_messages(LUser, LServer) -> + case odbc_queries:get_and_del_spool_msg_t(LServer, LUser) of + {atomic, {selected, Rs}} -> + {ok, lists:flatmap( + fun({_, XML}) -> + case xml_to_offline_msg(XML) of + {ok, Msg} -> + [Msg]; + _Err -> + [] + end + end, Rs)}; + Err -> + {error, Err} + end. + +remove_expired_messages(_LServer) -> + %% TODO + {atomic, ok}. + +remove_old_messages(Days, LServer) -> + case catch ejabberd_odbc:sql_query( + LServer, + [<<"DELETE FROM spool" + " WHERE created_at < " + "DATE_SUB(CURDATE(), INTERVAL ">>, + integer_to_list(Days), <<" DAY);">>]) of + {updated, N} -> + ?INFO_MSG("~p message(s) deleted from offline spool", [N]); + _Error -> + ?ERROR_MSG("Cannot delete message in offline spool: ~p", [_Error]) + end, + {atomic, ok}. + +remove_user(LUser, LServer) -> + odbc_queries:del_spool_msg(LServer, LUser). + +read_message_headers(LUser, LServer) -> + Username = ejabberd_odbc:escape(LUser), + case catch ejabberd_odbc:sql_query( + LServer, [<<"select xml, seq from spool where username ='">>, + Username, <<"' order by seq;">>]) of + {selected, [<<"xml">>, <<"seq">>], Rows} -> + lists:flatmap( + fun([XML, Seq]) -> + case xml_to_offline_msg(XML) of + {ok, #offline_msg{from = From, + to = To, + packet = El}} -> + Seq0 = binary_to_integer(Seq), + [{Seq0, From, To, El}]; + _ -> + [] + end + end, Rows); + _Err -> + [] + end. + +read_message(LUser, LServer, Seq) -> + Username = ejabberd_odbc:escape(LUser), + SSeq = ejabberd_odbc:escape(integer_to_binary(Seq)), + case ejabberd_odbc:sql_query( + LServer, + [<<"select xml from spool where username='">>, Username, + <<"' and seq='">>, SSeq, <<"';">>]) of + {selected, [<<"xml">>], [[RawXML]|_]} -> + case xml_to_offline_msg(RawXML) of + {ok, Msg} -> + {ok, Msg}; + _ -> + error + end; + _ -> + error + end. + +remove_message(LUser, LServer, Seq) -> + Username = ejabberd_odbc:escape(LUser), + SSeq = ejabberd_odbc:escape(integer_to_binary(Seq)), + ejabberd_odbc:sql_query( + LServer, + [<<"delete from spool where username='">>, Username, + <<"' and seq='">>, SSeq, <<"';">>]), + ok. + +read_all_messages(LUser, LServer) -> + case catch ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(xml)s from spool where " + "username=%(LUser)s order by seq")) of + {selected, Rs} -> + lists:flatmap( + fun({XML}) -> + case xml_to_offline_msg(XML) of + {ok, Msg} -> [Msg]; + _ -> [] + end + end, Rs); + _ -> + [] + end. + +remove_all_messages(LUser, LServer) -> + odbc_queries:del_spool_msg(LServer, LUser), + {atomic, ok}. + +count_messages(LUser, LServer) -> + case catch ejabberd_odbc:sql_query( + LServer, + ?SQL("select @(count(*))d from spool " + "where username=%(LUser)s")) of + {selected, [{Res}]} -> + Res; + _ -> 0 + end. + +export(_Server) -> + [{offline_msg, + fun(Host, #offline_msg{us = {LUser, LServer}, + timestamp = TimeStamp, from = From, to = To, + packet = Packet}) + when LServer == Host -> + Username = ejabberd_odbc:escape(LUser), + Packet1 = jlib:replace_from_to(From, To, Packet), + Packet2 = jlib:add_delay_info(Packet1, LServer, TimeStamp, + <<"Offline Storage">>), + XML = ejabberd_odbc:escape(fxml:element_to_binary(Packet2)), + [[<<"delete from spool where username='">>, Username, <<"';">>], + [<<"insert into spool(username, xml) values ('">>, + Username, <<"', '">>, XML, <<"');">>]]; + (_Host, _R) -> + [] + end}]. + +import(LServer) -> + [{<<"select username, xml from spool;">>, + fun([LUser, XML]) -> + El = #xmlel{} = fxml_stream:parse_element(XML), + From = #jid{} = jid:from_string( + fxml:get_attr_s(<<"from">>, El#xmlel.attrs)), + To = #jid{} = jid:from_string( + fxml:get_attr_s(<<"to">>, El#xmlel.attrs)), + Stamp = fxml:get_path_s(El, [{elem, <<"delay">>}, + {attr, <<"stamp">>}]), + TS = case jlib:datetime_string_to_timestamp(Stamp) of + {_, _, _} = Now -> + Now; + undefined -> + p1_time_compat:timestamp() + end, + Expire = mod_offline:find_x_expire(TS, El#xmlel.children), + #offline_msg{us = {LUser, LServer}, + from = From, to = To, + packet = El, + timestamp = TS, expire = Expire} + end}]. + +import(_, _) -> + pass. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +xml_to_offline_msg(XML) -> + case fxml_stream:parse_element(XML) of + #xmlel{} = El -> + el_to_offline_msg(El); + Err -> + ?ERROR_MSG("got ~p when parsing XML packet ~s", + [Err, XML]), + Err + end. + +el_to_offline_msg(El) -> + To_s = fxml:get_tag_attr_s(<<"to">>, El), + From_s = fxml:get_tag_attr_s(<<"from">>, El), + To = jid:from_string(To_s), + From = jid:from_string(From_s), + if To == error -> + ?ERROR_MSG("failed to get 'to' JID from offline XML ~p", [El]), + {error, bad_jid_to}; + From == error -> + ?ERROR_MSG("failed to get 'from' JID from offline XML ~p", [El]), + {error, bad_jid_from}; + true -> + {ok, #offline_msg{us = {To#jid.luser, To#jid.lserver}, + from = From, + to = To, + timestamp = undefined, + expire = undefined, + packet = El}} + end. From 52571e8624b0a48797ca24ec05e47a5c9477a12a Mon Sep 17 00:00:00 2001 From: Evgeniy Khramtsov Date: Fri, 15 Apr 2016 15:11:31 +0300 Subject: [PATCH 15/15] Clean mod_mam.erl from DB specific code --- src/mod_mam.erl | 541 ++++++----------------------------------- src/mod_mam_mnesia.erl | 178 ++++++++++++++ src/mod_mam_sql.erl | 309 +++++++++++++++++++++++ src/mod_muc.erl | 10 +- 4 files changed, 561 insertions(+), 477 deletions(-) create mode 100644 src/mod_mam_mnesia.erl create mode 100644 src/mod_mam_sql.erl diff --git a/src/mod_mam.erl b/src/mod_mam.erl index 862adee99..098ee8967 100644 --- a/src/mod_mam.erl +++ b/src/mod_mam.erl @@ -35,41 +35,37 @@ -export([user_send_packet/4, user_receive_packet/5, process_iq_v0_2/3, process_iq_v0_3/3, disco_sm_features/5, - remove_user/2, remove_user/3, mod_opt_type/1, muc_process_iq/4, + remove_user/2, remove_room/3, mod_opt_type/1, muc_process_iq/4, muc_filter_message/5, message_is_archived/5, delete_old_messages/2, - get_commands_spec/0]). + get_commands_spec/0, msg_to_el/4]). --include_lib("stdlib/include/ms_transform.hrl"). -include("jlib.hrl"). -include("logger.hrl"). -include("mod_muc_room.hrl"). -include("ejabberd_commands.hrl"). +-include("mod_mam.hrl"). -define(DEF_PAGE_SIZE, 50). -define(MAX_PAGE_SIZE, 250). --define(BIN_GREATER_THAN(A, B), - ((A > B andalso byte_size(A) == byte_size(B)) - orelse byte_size(A) > byte_size(B))). --define(BIN_LESS_THAN(A, B), - ((A < B andalso byte_size(A) == byte_size(B)) - orelse byte_size(A) < byte_size(B))). - --record(archive_msg, - {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$2', - id = <<>> :: binary() | '_', - timestamp = p1_time_compat:timestamp() :: erlang:timestamp() | '_' | '$1', - peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3' | undefined, - bare_peer = {<<"">>, <<"">>, <<"">>} :: ljid() | '_' | '$3', - packet = #xmlel{} :: xmlel() | '_', - nick = <<"">> :: binary(), - type = chat :: chat | groupchat}). - --record(archive_prefs, - {us = {<<"">>, <<"">>} :: {binary(), binary()}, - default = never :: never | always | roster, - always = [] :: [ljid()], - never = [] :: [ljid()]}). +-callback init(binary(), gen_mod:opts()) -> any(). +-callback remove_user(binary(), binary()) -> any(). +-callback remove_room(binary(), binary(), binary()) -> any(). +-callback delete_old_messages(binary() | global, + erlang:timestamp(), + all | chat | groupchat) -> any(). +-callback extended_fields() -> [xmlel()]. +-callback store(xmlel(), binary(), {binary(), binary()}, chat | groupchat, + jid(), binary(), recv | send) -> {ok, binary()} | any(). +-callback write_prefs(binary(), binary(), #archive_prefs{}, binary()) -> ok | any(). +-callback get_prefs(binary(), binary()) -> {ok, #archive_prefs{}} | error. +-callback select(binary(), jid(), jid(), + none | erlang:timestamp(), + none | erlang:timestamp(), + none | ljid() | {text, binary()}, + none | #rsm_in{}, + chat | groupchat) -> + {[{binary(), non_neg_integer(), xmlel()}], boolean(), non_neg_integer()}. %%%=================================================================== %%% API @@ -77,9 +73,9 @@ start(Host, Opts) -> IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1, one_queue), - DBType = gen_mod:db_type(Host, Opts), - init_db(DBType, Host), - init_cache(DBType, Opts), + Mod = gen_mod:db_mod(Host, Opts, ?MODULE), + Mod:init(Host, Opts), + init_cache(Opts), gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_MAM_TMP, ?MODULE, process_iq_v0_2, IQDisc), gen_iq_handler:add_iq_handler(ejabberd_sm, Host, @@ -120,18 +116,7 @@ start(Host, Opts) -> ejabberd_commands:register_commands(get_commands_spec()), ok. -init_db(mnesia, _Host) -> - mnesia:create_table(archive_msg, - [{disc_only_copies, [node()]}, - {type, bag}, - {attributes, record_info(fields, archive_msg)}]), - mnesia:create_table(archive_prefs, - [{disc_only_copies, [node()]}, - {attributes, record_info(fields, archive_prefs)}]); -init_db(_, _) -> - ok. - -init_cache(_DBType, Opts) -> +init_cache(Opts) -> MaxSize = gen_mod:get_opt(cache_size, Opts, fun(I) when is_integer(I), I>0 -> I end, 1000), @@ -179,24 +164,14 @@ stop(Host) -> remove_user(User, Server) -> LUser = jid:nodeprep(User), LServer = jid:nameprep(Server), - remove_user(LUser, LServer, - gen_mod:db_type(LServer, ?MODULE)). + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:remove_user(LUser, LServer). -remove_user(LUser, LServer, mnesia) -> - US = {LUser, LServer}, - F = fun () -> - mnesia:delete({archive_msg, US}), - mnesia:delete({archive_prefs, US}) - end, - mnesia:transaction(F); -remove_user(LUser, LServer, odbc) -> - SUser = ejabberd_odbc:escape(LUser), - ejabberd_odbc:sql_query( - LServer, - [<<"delete from archive where username='">>, SUser, <<"';">>]), - ejabberd_odbc:sql_query( - LServer, - [<<"delete from archive_prefs where username='">>, SUser, <<"';">>]). +remove_room(LServer, Name, Host) -> + LName = jid:nodeprep(Name), + LHost = jid:nameprep(Host), + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:remove_room(LServer, LName, LHost). user_receive_packet(Pkt, C2SState, JID, Peer, To) -> LUser = JID#jid.luser, @@ -343,10 +318,10 @@ message_is_archived(false, C2SState, Peer, if_enabled -> get_prefs(LUser, LServer); on_request -> - DBType = gen_mod:db_type(LServer, ?MODULE), + Mod = gen_mod:db_mod(LServer, ?MODULE), cache_tab:lookup(archive_prefs, {LUser, LServer}, fun() -> - get_prefs(LUser, LServer, DBType) + Mod:get_prefs(LUser, LServer) end); never -> error @@ -365,21 +340,19 @@ delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>; Diff = Days * 24 * 60 * 60 * 1000000, TimeStamp = usec_to_now(p1_time_compat:system_time(micro_seconds) - Diff), Type = jlib:binary_to_atom(TypeBin), - {Results, _} = - lists:foldl(fun(Host, {Results, MnesiaDone}) -> - case {gen_mod:db_type(Host, ?MODULE), MnesiaDone} of - {mnesia, true} -> - {Results, true}; - {mnesia, false} -> - Res = delete_old_messages(TimeStamp, Type, - global, mnesia), - {[Res|Results], true}; - {DBType, _} -> - Res = delete_old_messages(TimeStamp, Type, - Host, DBType), - {[Res|Results], MnesiaDone} - end - end, {[], false}, ?MYHOSTS), + DBTypes = lists:usort( + lists:map( + fun(Host) -> + case gen_mod:db_type(Host, ?MODULE) of + odbc -> {odbc, Host}; + Other -> {Other, global} + end + end, ?MYHOSTS)), + Results = lists:map( + fun({DBType, ServerHost}) -> + Mod = gen_mod:db_mod(DBType, ?MODULE), + Mod:delete_old_messages(ServerHost, TimeStamp, Type) + end, DBTypes), case lists:filter(fun(Res) -> Res /= ok end, Results) of [] -> ok; [NotOk|_] -> NotOk @@ -387,21 +360,6 @@ delete_old_messages(TypeBin, Days) when TypeBin == <<"chat">>; delete_old_messages(_TypeBin, _Days) -> unsupported_type. -delete_old_messages(TimeStamp, Type, global, mnesia) -> - MS = ets:fun2ms(fun(#archive_msg{timestamp = MsgTS, - type = MsgType} = Msg) - when MsgTS < TimeStamp, - MsgType == Type orelse Type == all -> - Msg - end), - OldMsgs = mnesia:dirty_select(archive_msg, MS), - lists:foreach(fun(Rec) -> - ok = mnesia:dirty_delete_object(Rec) - end, OldMsgs); -delete_old_messages(_TimeStamp, _Type, _Host, _DBType) -> - %% TODO - not_implemented. - %%%=================================================================== %%% Internal functions %%%=================================================================== @@ -427,15 +385,9 @@ process_iq(LServer, #iq{sub_el = #xmlel{attrs = Attrs}} = IQ) -> #xmlel{name = <<"field">>, attrs = [{<<"type">>, <<"text-single">>}, {<<"var">>, <<"end">>}]}], - Fields = case gen_mod:db_type(LServer, ?MODULE) of - odbc -> - WithText = #xmlel{name = <<"field">>, - attrs = [{<<"type">>, <<"text-single">>}, - {<<"var">>, <<"withtext">>}]}, - [WithText|CommonFields]; - _ -> - CommonFields - end, + Mod = gen_mod:db_mod(LServer, ?MODULE), + ExtendedFields = Mod:extended_fields(), + Fields = ExtendedFields ++ CommonFields, Form = #xmlel{name = <<"x">>, attrs = [{<<"xmlns">>, ?NS_XDATA}, {<<"type">>, <<"form">>}], children = Fields}, @@ -715,8 +667,8 @@ store_msg(C2SState, Pkt, LUser, LServer, Peer, Dir) -> case should_archive_peer(C2SState, Prefs, Peer) of true -> US = {LUser, LServer}, - store(Pkt, LServer, US, chat, Peer, <<"">>, Dir, - gen_mod:db_type(LServer, ?MODULE)); + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:store(Pkt, LServer, US, chat, Peer, <<"">>, Dir); false -> pass end. @@ -726,101 +678,26 @@ store_muc(MUCState, Pkt, RoomJID, Peer, Nick) -> true -> LServer = MUCState#state.server_host, {U, S, _} = jid:tolower(RoomJID), - store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv, - gen_mod:db_type(LServer, ?MODULE)); + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:store(Pkt, LServer, {U, S}, groupchat, Peer, Nick, recv); false -> pass end. -store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir, mnesia) -> - LPeer = {PUser, PServer, _} = jid:tolower(Peer), - TS = p1_time_compat:timestamp(), - ID = jlib:integer_to_binary(now_to_usec(TS)), - case mnesia:dirty_write( - #archive_msg{us = {LUser, LServer}, - id = ID, - timestamp = TS, - peer = LPeer, - bare_peer = {PUser, PServer, <<>>}, - type = Type, - nick = Nick, - packet = Pkt}) of - ok -> - {ok, ID}; - Err -> - Err - end; -store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir, odbc) -> - TSinteger = p1_time_compat:system_time(micro_seconds), - ID = TS = jlib:integer_to_binary(TSinteger), - SUser = case Type of - chat -> LUser; - groupchat -> jid:to_string({LUser, LHost, <<>>}) - end, - BarePeer = jid:to_string( - jid:tolower( - jid:remove_resource(Peer))), - LPeer = jid:to_string( - jid:tolower(Peer)), - XML = fxml:element_to_binary(Pkt), - Body = fxml:get_subtag_cdata(Pkt, <<"body">>), - case ejabberd_odbc:sql_query( - LServer, - [<<"insert into archive (username, timestamp, " - "peer, bare_peer, xml, txt, kind, nick) values (">>, - <<"'">>, ejabberd_odbc:escape(SUser), <<"', ">>, - <<"'">>, TS, <<"', ">>, - <<"'">>, ejabberd_odbc:escape(LPeer), <<"', ">>, - <<"'">>, ejabberd_odbc:escape(BarePeer), <<"', ">>, - <<"'">>, ejabberd_odbc:escape(XML), <<"', ">>, - <<"'">>, ejabberd_odbc:escape(Body), <<"', ">>, - <<"'">>, jlib:atom_to_binary(Type), <<"', ">>, - <<"'">>, ejabberd_odbc:escape(Nick), <<"');">>]) of - {updated, _} -> - {ok, ID}; - Err -> - Err - end. - write_prefs(LUser, LServer, Host, Default, Always, Never) -> - DBType = case gen_mod:db_type(Host, ?MODULE) of - odbc -> {odbc, Host}; - DB -> DB - end, Prefs = #archive_prefs{us = {LUser, LServer}, default = Default, always = Always, never = Never}, + Mod = gen_mod:db_mod(Host, ?MODULE), cache_tab:dirty_insert( archive_prefs, {LUser, LServer}, Prefs, - fun() -> write_prefs(LUser, LServer, Prefs, DBType) end). - -write_prefs(_LUser, _LServer, Prefs, mnesia) -> - mnesia:dirty_write(Prefs); -write_prefs(LUser, _LServer, #archive_prefs{default = Default, - never = Never, - always = Always}, - {odbc, Host}) -> - SUser = ejabberd_odbc:escape(LUser), - SDefault = erlang:atom_to_binary(Default, utf8), - SAlways = ejabberd_odbc:encode_term(Always), - SNever = ejabberd_odbc:encode_term(Never), - case update(Host, <<"archive_prefs">>, - [<<"username">>, <<"def">>, <<"always">>, <<"never">>], - [SUser, SDefault, SAlways, SNever], - [<<"username='">>, SUser, <<"'">>]) of - {updated, _} -> - ok; - Err -> - Err - end. + fun() -> Mod:write_prefs(LUser, LServer, Prefs, Host) end). get_prefs(LUser, LServer) -> - DBType = gen_mod:db_type(LServer, ?MODULE), + Mod = gen_mod:db_mod(LServer, ?MODULE), Res = cache_tab:lookup(archive_prefs, {LUser, LServer}, - fun() -> get_prefs(LUser, LServer, - DBType) - end), + fun() -> Mod:get_prefs(LUser, LServer) end), case Res of {ok, Prefs} -> Prefs; @@ -842,31 +719,6 @@ get_prefs(LUser, LServer) -> end end. -get_prefs(LUser, LServer, mnesia) -> - case mnesia:dirty_read(archive_prefs, {LUser, LServer}) of - [Prefs] -> - {ok, Prefs}; - _ -> - error - end; -get_prefs(LUser, LServer, odbc) -> - case ejabberd_odbc:sql_query( - LServer, - [<<"select def, always, never from archive_prefs ">>, - <<"where username='">>, - ejabberd_odbc:escape(LUser), <<"';">>]) of - {selected, _, [[SDefault, SAlways, SNever]]} -> - Default = erlang:binary_to_existing_atom(SDefault, utf8), - Always = ejabberd_odbc:decode_term(SAlways), - Never = ejabberd_odbc:decode_term(SNever), - {ok, #archive_prefs{us = {LUser, LServer}, - default = Default, - always = Always, - never = Never}}; - _ -> - error - end. - prefs_el(Default, Always, Never, NS) -> Default1 = jlib:atom_to_binary(Default), JFun = fun(L) -> @@ -890,11 +742,10 @@ maybe_activate_mam(LUser, LServer) -> false), case ActivateOpt of true -> + Mod = gen_mod:db_mod(LServer, ?MODULE), Res = cache_tab:lookup(archive_prefs, {LUser, LServer}, fun() -> - get_prefs(LUser, LServer, - gen_mod:db_type(LServer, - ?MODULE)) + Mod:get_prefs(LUser, LServer) end), case Res of {ok, _Prefs} -> @@ -912,31 +763,22 @@ maybe_activate_mam(LUser, LServer) -> end. select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType) -> - DBType = case gen_mod:db_type(LServer, ?MODULE) of - odbc -> {odbc, LServer}; - DB -> DB - end, - select_and_send(LServer, From, To, Start, End, With, RSM, IQ, - MsgType, DBType). - -select_and_send(LServer, From, To, Start, End, With, RSM, IQ, MsgType, DBType) -> {Msgs, IsComplete, Count} = select_and_start(LServer, From, To, Start, End, - With, RSM, MsgType, DBType), + With, RSM, MsgType), SortedMsgs = lists:keysort(2, Msgs), send(From, To, SortedMsgs, RSM, Count, IsComplete, IQ). -select_and_start(LServer, From, To, Start, End, With, RSM, MsgType, DBType) -> +select_and_start(LServer, From, To, Start, End, With, RSM, MsgType) -> case MsgType of chat -> - select(LServer, From, From, Start, End, With, RSM, MsgType, DBType); + select(LServer, From, From, Start, End, With, RSM, MsgType); {groupchat, _Role, _MUCState} -> - select(LServer, From, To, Start, End, With, RSM, MsgType, DBType) + select(LServer, From, To, Start, End, With, RSM, MsgType) end. select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM, {groupchat, _Role, #state{config = #config{mam = false}, - history = History}} = MsgType, - _DBType) -> + history = History}} = MsgType) -> #lqueue{len = L, queue = Q} = History, {Msgs0, _} = lists:mapfoldl( @@ -970,81 +812,9 @@ select(_LServer, JidRequestor, JidArchive, Start, End, _With, RSM, _ -> {Msgs, true, L} end; -select(_LServer, JidRequestor, - #jid{luser = LUser, lserver = LServer} = JidArchive, - Start, End, With, RSM, MsgType, mnesia) -> - MS = make_matchspec(LUser, LServer, Start, End, With), - Msgs = mnesia:dirty_select(archive_msg, MS), - SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs), - {FilteredMsgs, IsComplete} = filter_by_rsm(SortedMsgs, RSM), - Count = length(Msgs), - {lists:map( - fun(Msg) -> - {Msg#archive_msg.id, - jlib:binary_to_integer(Msg#archive_msg.id), - msg_to_el(Msg, MsgType, JidRequestor, JidArchive)} - end, FilteredMsgs), IsComplete, Count}; -select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, - Start, End, With, RSM, MsgType, {odbc, Host}) -> - User = case MsgType of - chat -> LUser; - {groupchat, _Role, _MUCState} -> jid:to_string(JidArchive) - end, - {Query, CountQuery} = make_sql_query(User, LServer, - Start, End, With, RSM), - % TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a - % reasonable limit on how many stanzas may be pushed to a client in one - % request. If a query returns a number of stanzas greater than this limit - % and the client did not specify a limit using RSM then the server should - % return a policy-violation error to the client." We currently don't do this - % for v0.2 requests, but we do limit #rsm_in.max for v0.3 and newer. - case {ejabberd_odbc:sql_query(Host, Query), - ejabberd_odbc:sql_query(Host, CountQuery)} of - {{selected, _, Res}, {selected, _, [[Count]]}} -> - {Max, Direction} = case RSM of - #rsm_in{max = M, direction = D} -> {M, D}; - _ -> {undefined, undefined} - end, - {Res1, IsComplete} = - if Max >= 0 andalso Max /= undefined andalso length(Res) > Max -> - if Direction == before -> - {lists:nthtail(1, Res), false}; - true -> - {lists:sublist(Res, Max), false} - end; - true -> - {Res, true} - end, - {lists:flatmap( - fun([TS, XML, PeerBin, Kind, Nick]) -> - try - #xmlel{} = El = fxml_stream:parse_element(XML), - Now = usec_to_now(jlib:binary_to_integer(TS)), - PeerJid = jid:tolower(jid:from_string(PeerBin)), - T = case Kind of - <<"">> -> chat; - null -> chat; - _ -> jlib:binary_to_atom(Kind) - end, - [{TS, jlib:binary_to_integer(TS), - msg_to_el(#archive_msg{timestamp = Now, - packet = El, - type = T, - nick = Nick, - peer = PeerJid}, - MsgType, JidRequestor, JidArchive)}] - catch _:Err -> - ?ERROR_MSG("failed to parse data from SQL: ~p. " - "The data was: " - "timestamp = ~s, xml = ~s, " - "peer = ~s, kind = ~s, nick = ~s", - [Err, TS, XML, PeerBin, Kind, Nick]), - [] - end - end, Res1), IsComplete, jlib:binary_to_integer(Count)}; - _ -> - {[], false, 0} - end. +select(LServer, From, From, Start, End, With, RSM, MsgType) -> + Mod = gen_mod:db_mod(LServer, ?MODULE), + Mod:select(LServer, From, From, Start, End, With, RSM, MsgType). msg_to_el(#archive_msg{timestamp = TS, packet = Pkt1, nick = Nick, peer = Peer}, MsgType, JidRequestor, #jid{lserver = LServer} = JidArchive) -> @@ -1160,7 +930,6 @@ send(From, To, Msgs, RSM, Count, IsComplete, #iq{sub_el = SubEl} = IQ) -> ignore end. - make_rsm_out([], _, Count, Attrs, NS) -> Tag = if NS == ?NS_MAM_TMP -> <<"query">>; true -> <<"fin">> @@ -1177,32 +946,6 @@ make_rsm_out([{FirstID, _, _}|_] = Msgs, _, Count, Attrs, NS) -> #rsm_out{first = FirstID, count = Count, last = LastID})}]. -filter_by_rsm(Msgs, none) -> - {Msgs, true}; -filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 -> - {[], true}; -filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) -> - NewMsgs = case Direction of - aft when ID /= <<"">> -> - lists:filter( - fun(#archive_msg{id = I}) -> - ?BIN_GREATER_THAN(I, ID) - end, Msgs); - before when ID /= <<"">> -> - lists:foldl( - fun(#archive_msg{id = I} = Msg, Acc) - when ?BIN_LESS_THAN(I, ID) -> - [Msg|Acc]; - (_, Acc) -> - Acc - end, [], Msgs); - before when ID == <<"">> -> - lists:reverse(Msgs); - _ -> - Msgs - end, - filter_by_max(NewMsgs, Max). - filter_by_max(Msgs, undefined) -> {Msgs, true}; filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 -> @@ -1231,126 +974,6 @@ match_rsm(Now, #rsm_in{id = ID, direction = before}) when ID /= <<"">> -> match_rsm(_Now, _) -> true. -make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) -> - ets:fun2ms( - fun(#archive_msg{timestamp = TS, - us = US, - bare_peer = BPeer} = Msg) - when Start =< TS, End >= TS, - US == {LUser, LServer}, - BPeer == With -> - Msg - end); -make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) -> - ets:fun2ms( - fun(#archive_msg{timestamp = TS, - us = US, - peer = Peer} = Msg) - when Start =< TS, End >= TS, - US == {LUser, LServer}, - Peer == With -> - Msg - end); -make_matchspec(LUser, LServer, Start, End, none) -> - ets:fun2ms( - fun(#archive_msg{timestamp = TS, - us = US, - peer = Peer} = Msg) - when Start =< TS, End >= TS, - US == {LUser, LServer} -> - Msg - end). - -make_sql_query(User, LServer, Start, End, With, RSM) -> - {Max, Direction, ID} = case RSM of - #rsm_in{} -> - {RSM#rsm_in.max, - RSM#rsm_in.direction, - RSM#rsm_in.id}; - none -> - {none, none, <<>>} - end, - ODBCType = ejabberd_config:get_option( - {odbc_type, LServer}, - ejabberd_odbc:opt_type(odbc_type)), - LimitClause = if is_integer(Max), Max >= 0, ODBCType /= mssql -> - [<<" limit ">>, jlib:integer_to_binary(Max+1)]; - true -> - [] - end, - TopClause = if is_integer(Max), Max >= 0, ODBCType == mssql -> - [<<" TOP ">>, jlib:integer_to_binary(Max+1)]; - true -> - [] - end, - WithClause = case With of - {text, <<>>} -> - []; - {text, Txt} -> - [<<" and match (txt) against ('">>, - ejabberd_odbc:escape(Txt), <<"')">>]; - {_, _, <<>>} -> - [<<" and bare_peer='">>, - ejabberd_odbc:escape(jid:to_string(With)), - <<"'">>]; - {_, _, _} -> - [<<" and peer='">>, - ejabberd_odbc:escape(jid:to_string(With)), - <<"'">>]; - none -> - [] - end, - PageClause = case catch jlib:binary_to_integer(ID) of - I when is_integer(I), I >= 0 -> - case Direction of - before -> - [<<" AND timestamp < ">>, ID]; - aft -> - [<<" AND timestamp > ">>, ID]; - _ -> - [] - end; - _ -> - [] - end, - StartClause = case Start of - {_, _, _} -> - [<<" and timestamp >= ">>, - jlib:integer_to_binary(now_to_usec(Start))]; - _ -> - [] - end, - EndClause = case End of - {_, _, _} -> - [<<" and timestamp <= ">>, - jlib:integer_to_binary(now_to_usec(End))]; - _ -> - [] - end, - SUser = ejabberd_odbc:escape(User), - - Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick" - " FROM archive WHERE username='">>, - SUser, <<"'">>, WithClause, StartClause, EndClause, - PageClause], - - QueryPage = - case Direction of - before -> - % ID can be empty because of - % XEP-0059: Result Set Management - % 2.5 Requesting the Last Page in a Result Set - [<<"SELECT timestamp, xml, peer, kind, nick FROM (">>, Query, - <<" ORDER BY timestamp DESC ">>, - LimitClause, <<") AS t ORDER BY timestamp ASC;">>]; - _ -> - [Query, <<" ORDER BY timestamp ASC ">>, - LimitClause, <<";">>] - end, - {QueryPage, - [<<"SELECT COUNT(*) FROM archive WHERE username='">>, - SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}. - now_to_usec({MSec, Sec, USec}) -> (MSec*1000000 + Sec)*1000000 + USec. @@ -1376,28 +999,6 @@ get_jids(Els) -> [] end, Els). -update(LServer, Table, Fields, Vals, Where) -> - UPairs = lists:zipwith(fun (A, B) -> - <> - end, - Fields, Vals), - case ejabberd_odbc:sql_query(LServer, - [<<"update ">>, Table, <<" set ">>, - join(UPairs, <<", ">>), <<" where ">>, Where, - <<";">>]) - of - {updated, 1} -> {updated, 1}; - _ -> - ejabberd_odbc:sql_query(LServer, - [<<"insert into ">>, Table, <<"(">>, - join(Fields, <<", ">>), <<") values ('">>, - join(Vals, <<"', '">>), <<"');">>]) - end. - -%% Almost a copy of string:join/2. -join([], _Sep) -> []; -join([H | T], Sep) -> [H, [[Sep, X] || X <- T]]. - get_commands_spec() -> [#ejabberd_commands{name = delete_old_mam_messages, tags = [purge], desc = "Delete MAM messages older than DAYS", @@ -1416,7 +1017,11 @@ mod_opt_type(cache_life_time) -> fun (I) when is_integer(I), I > 0 -> I end; mod_opt_type(cache_size) -> fun (I) when is_integer(I), I > 0 -> I end; -mod_opt_type(db_type) -> fun gen_mod:v_db/1; +mod_opt_type(db_type) -> + fun(odbc) -> odbc; + (internal) -> mnesia; + (mnesia) -> mnesia + end; mod_opt_type(default) -> fun (always) -> always; (never) -> never; diff --git a/src/mod_mam_mnesia.erl b/src/mod_mam_mnesia.erl new file mode 100644 index 000000000..007ef5eba --- /dev/null +++ b/src/mod_mam_mnesia.erl @@ -0,0 +1,178 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 15 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_mam_mnesia). + +-behaviour(mod_mam). + +%% API +-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3, + extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]). + +-include_lib("stdlib/include/ms_transform.hrl"). +-include("jlib.hrl"). +-include("mod_mam.hrl"). + +-define(BIN_GREATER_THAN(A, B), + ((A > B andalso byte_size(A) == byte_size(B)) + orelse byte_size(A) > byte_size(B))). +-define(BIN_LESS_THAN(A, B), + ((A < B andalso byte_size(A) == byte_size(B)) + orelse byte_size(A) < byte_size(B))). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + mnesia:create_table(archive_msg, + [{disc_only_copies, [node()]}, + {type, bag}, + {attributes, record_info(fields, archive_msg)}]), + mnesia:create_table(archive_prefs, + [{disc_only_copies, [node()]}, + {attributes, record_info(fields, archive_prefs)}]). + +remove_user(LUser, LServer) -> + US = {LUser, LServer}, + F = fun () -> + mnesia:delete({archive_msg, US}), + mnesia:delete({archive_prefs, US}) + end, + mnesia:transaction(F). + +remove_room(_LServer, LName, LHost) -> + remove_user(LName, LHost). + +delete_old_messages(global, TimeStamp, Type) -> + MS = ets:fun2ms(fun(#archive_msg{timestamp = MsgTS, + type = MsgType} = Msg) + when MsgTS < TimeStamp, + MsgType == Type orelse Type == all -> + Msg + end), + OldMsgs = mnesia:dirty_select(archive_msg, MS), + lists:foreach(fun(Rec) -> + ok = mnesia:dirty_delete_object(Rec) + end, OldMsgs). + +extended_fields() -> + []. + +store(Pkt, _, {LUser, LServer}, Type, Peer, Nick, _Dir) -> + LPeer = {PUser, PServer, _} = jid:tolower(Peer), + TS = p1_time_compat:timestamp(), + ID = jlib:integer_to_binary(now_to_usec(TS)), + case mnesia:dirty_write( + #archive_msg{us = {LUser, LServer}, + id = ID, + timestamp = TS, + peer = LPeer, + bare_peer = {PUser, PServer, <<>>}, + type = Type, + nick = Nick, + packet = Pkt}) of + ok -> + {ok, ID}; + Err -> + Err + end. + +write_prefs(_LUser, _LServer, Prefs, _ServerHost) -> + mnesia:dirty_write(Prefs). + +get_prefs(LUser, LServer) -> + case mnesia:dirty_read(archive_prefs, {LUser, LServer}) of + [Prefs] -> + {ok, Prefs}; + _ -> + error + end. + +select(_LServer, JidRequestor, + #jid{luser = LUser, lserver = LServer} = JidArchive, + Start, End, With, RSM, MsgType) -> + MS = make_matchspec(LUser, LServer, Start, End, With), + Msgs = mnesia:dirty_select(archive_msg, MS), + SortedMsgs = lists:keysort(#archive_msg.timestamp, Msgs), + {FilteredMsgs, IsComplete} = filter_by_rsm(SortedMsgs, RSM), + Count = length(Msgs), + {lists:map( + fun(Msg) -> + {Msg#archive_msg.id, + jlib:binary_to_integer(Msg#archive_msg.id), + mod_mam:msg_to_el(Msg, MsgType, JidRequestor, JidArchive)} + end, FilteredMsgs), IsComplete, Count}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +now_to_usec({MSec, Sec, USec}) -> + (MSec*1000000 + Sec)*1000000 + USec. + +make_matchspec(LUser, LServer, Start, End, {_, _, <<>>} = With) -> + ets:fun2ms( + fun(#archive_msg{timestamp = TS, + us = US, + bare_peer = BPeer} = Msg) + when Start =< TS, End >= TS, + US == {LUser, LServer}, + BPeer == With -> + Msg + end); +make_matchspec(LUser, LServer, Start, End, {_, _, _} = With) -> + ets:fun2ms( + fun(#archive_msg{timestamp = TS, + us = US, + peer = Peer} = Msg) + when Start =< TS, End >= TS, + US == {LUser, LServer}, + Peer == With -> + Msg + end); +make_matchspec(LUser, LServer, Start, End, none) -> + ets:fun2ms( + fun(#archive_msg{timestamp = TS, + us = US, + peer = Peer} = Msg) + when Start =< TS, End >= TS, + US == {LUser, LServer} -> + Msg + end). + +filter_by_rsm(Msgs, none) -> + {Msgs, true}; +filter_by_rsm(_Msgs, #rsm_in{max = Max}) when Max < 0 -> + {[], true}; +filter_by_rsm(Msgs, #rsm_in{max = Max, direction = Direction, id = ID}) -> + NewMsgs = case Direction of + aft when ID /= <<"">> -> + lists:filter( + fun(#archive_msg{id = I}) -> + ?BIN_GREATER_THAN(I, ID) + end, Msgs); + before when ID /= <<"">> -> + lists:foldl( + fun(#archive_msg{id = I} = Msg, Acc) + when ?BIN_LESS_THAN(I, ID) -> + [Msg|Acc]; + (_, Acc) -> + Acc + end, [], Msgs); + before when ID == <<"">> -> + lists:reverse(Msgs); + _ -> + Msgs + end, + filter_by_max(NewMsgs, Max). + +filter_by_max(Msgs, undefined) -> + {Msgs, true}; +filter_by_max(Msgs, Len) when is_integer(Len), Len >= 0 -> + {lists:sublist(Msgs, Len), length(Msgs) =< Len}; +filter_by_max(_Msgs, _Junk) -> + {[], true}. diff --git a/src/mod_mam_sql.erl b/src/mod_mam_sql.erl new file mode 100644 index 000000000..1f24de317 --- /dev/null +++ b/src/mod_mam_sql.erl @@ -0,0 +1,309 @@ +%%%------------------------------------------------------------------- +%%% @author Evgeny Khramtsov +%%% @copyright (C) 2016, Evgeny Khramtsov +%%% @doc +%%% +%%% @end +%%% Created : 15 Apr 2016 by Evgeny Khramtsov +%%%------------------------------------------------------------------- +-module(mod_mam_sql). + +-behaviour(mod_mam). + +%% API +-export([init/2, remove_user/2, remove_room/3, delete_old_messages/3, + extended_fields/0, store/7, write_prefs/4, get_prefs/2, select/8]). + +-include_lib("stdlib/include/ms_transform.hrl"). +-include("jlib.hrl"). +-include("mod_mam.hrl"). +-include("logger.hrl"). + +%%%=================================================================== +%%% API +%%%=================================================================== +init(_Host, _Opts) -> + ok. + +remove_user(LUser, LServer) -> + SUser = ejabberd_odbc:escape(LUser), + ejabberd_odbc:sql_query( + LServer, + [<<"delete from archive where username='">>, SUser, <<"';">>]), + ejabberd_odbc:sql_query( + LServer, + [<<"delete from archive_prefs where username='">>, SUser, <<"';">>]). + +remove_room(LServer, LName, LHost) -> + LUser = jid:to_string({LName, LHost, <<>>}), + remove_user(LUser, LServer). + +delete_old_messages(ServerHost, TimeStamp, Type) -> + TypeClause = if Type == all -> <<"">>; + true -> [<<" and kind='">>, jlib:atom_to_binary(Type), <<"'">>] + end, + TS = integer_to_binary(now_to_usec(TimeStamp)), + ejabberd_odbc:sql_query( + ServerHost, [<<"delete from archive where timestamp<">>, + TS, TypeClause, <<";">>]), + ok. + +extended_fields() -> + [#xmlel{name = <<"field">>, + attrs = [{<<"type">>, <<"text-single">>}, + {<<"var">>, <<"withtext">>}]}]. + +store(Pkt, LServer, {LUser, LHost}, Type, Peer, Nick, _Dir) -> + TSinteger = p1_time_compat:system_time(micro_seconds), + ID = TS = jlib:integer_to_binary(TSinteger), + SUser = case Type of + chat -> LUser; + groupchat -> jid:to_string({LUser, LHost, <<>>}) + end, + BarePeer = jid:to_string( + jid:tolower( + jid:remove_resource(Peer))), + LPeer = jid:to_string( + jid:tolower(Peer)), + XML = fxml:element_to_binary(Pkt), + Body = fxml:get_subtag_cdata(Pkt, <<"body">>), + case ejabberd_odbc:sql_query( + LServer, + [<<"insert into archive (username, timestamp, " + "peer, bare_peer, xml, txt, kind, nick) values (">>, + <<"'">>, ejabberd_odbc:escape(SUser), <<"', ">>, + <<"'">>, TS, <<"', ">>, + <<"'">>, ejabberd_odbc:escape(LPeer), <<"', ">>, + <<"'">>, ejabberd_odbc:escape(BarePeer), <<"', ">>, + <<"'">>, ejabberd_odbc:escape(XML), <<"', ">>, + <<"'">>, ejabberd_odbc:escape(Body), <<"', ">>, + <<"'">>, jlib:atom_to_binary(Type), <<"', ">>, + <<"'">>, ejabberd_odbc:escape(Nick), <<"');">>]) of + {updated, _} -> + {ok, ID}; + Err -> + Err + end. + +write_prefs(LUser, _LServer, #archive_prefs{default = Default, + never = Never, + always = Always}, + ServerHost) -> + SUser = ejabberd_odbc:escape(LUser), + SDefault = erlang:atom_to_binary(Default, utf8), + SAlways = ejabberd_odbc:encode_term(Always), + SNever = ejabberd_odbc:encode_term(Never), + case update(ServerHost, <<"archive_prefs">>, + [<<"username">>, <<"def">>, <<"always">>, <<"never">>], + [SUser, SDefault, SAlways, SNever], + [<<"username='">>, SUser, <<"'">>]) of + {updated, _} -> + ok; + Err -> + Err + end. + +get_prefs(LUser, LServer) -> + case ejabberd_odbc:sql_query( + LServer, + [<<"select def, always, never from archive_prefs ">>, + <<"where username='">>, + ejabberd_odbc:escape(LUser), <<"';">>]) of + {selected, _, [[SDefault, SAlways, SNever]]} -> + Default = erlang:binary_to_existing_atom(SDefault, utf8), + Always = ejabberd_odbc:decode_term(SAlways), + Never = ejabberd_odbc:decode_term(SNever), + {ok, #archive_prefs{us = {LUser, LServer}, + default = Default, + always = Always, + never = Never}}; + _ -> + error + end. + +select(LServer, JidRequestor, #jid{luser = LUser} = JidArchive, + Start, End, With, RSM, MsgType) -> + User = case MsgType of + chat -> LUser; + {groupchat, _Role, _MUCState} -> jid:to_string(JidArchive) + end, + {Query, CountQuery} = make_sql_query(User, LServer, + Start, End, With, RSM), + % TODO from XEP-0313 v0.2: "To conserve resources, a server MAY place a + % reasonable limit on how many stanzas may be pushed to a client in one + % request. If a query returns a number of stanzas greater than this limit + % and the client did not specify a limit using RSM then the server should + % return a policy-violation error to the client." We currently don't do this + % for v0.2 requests, but we do limit #rsm_in.max for v0.3 and newer. + case {ejabberd_odbc:sql_query(LServer, Query), + ejabberd_odbc:sql_query(LServer, CountQuery)} of + {{selected, _, Res}, {selected, _, [[Count]]}} -> + {Max, Direction} = case RSM of + #rsm_in{max = M, direction = D} -> {M, D}; + _ -> {undefined, undefined} + end, + {Res1, IsComplete} = + if Max >= 0 andalso Max /= undefined andalso length(Res) > Max -> + if Direction == before -> + {lists:nthtail(1, Res), false}; + true -> + {lists:sublist(Res, Max), false} + end; + true -> + {Res, true} + end, + {lists:flatmap( + fun([TS, XML, PeerBin, Kind, Nick]) -> + try + #xmlel{} = El = fxml_stream:parse_element(XML), + Now = usec_to_now(jlib:binary_to_integer(TS)), + PeerJid = jid:tolower(jid:from_string(PeerBin)), + T = case Kind of + <<"">> -> chat; + null -> chat; + _ -> jlib:binary_to_atom(Kind) + end, + [{TS, jlib:binary_to_integer(TS), + mod_mam:msg_to_el(#archive_msg{timestamp = Now, + packet = El, + type = T, + nick = Nick, + peer = PeerJid}, + MsgType, JidRequestor, JidArchive)}] + catch _:Err -> + ?ERROR_MSG("failed to parse data from SQL: ~p. " + "The data was: " + "timestamp = ~s, xml = ~s, " + "peer = ~s, kind = ~s, nick = ~s", + [Err, TS, XML, PeerBin, Kind, Nick]), + [] + end + end, Res1), IsComplete, jlib:binary_to_integer(Count)}; + _ -> + {[], false, 0} + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== +now_to_usec({MSec, Sec, USec}) -> + (MSec*1000000 + Sec)*1000000 + USec. + +usec_to_now(Int) -> + Secs = Int div 1000000, + USec = Int rem 1000000, + MSec = Secs div 1000000, + Sec = Secs rem 1000000, + {MSec, Sec, USec}. + +make_sql_query(User, LServer, Start, End, With, RSM) -> + {Max, Direction, ID} = case RSM of + #rsm_in{} -> + {RSM#rsm_in.max, + RSM#rsm_in.direction, + RSM#rsm_in.id}; + none -> + {none, none, <<>>} + end, + ODBCType = ejabberd_config:get_option( + {odbc_type, LServer}, + ejabberd_odbc:opt_type(odbc_type)), + LimitClause = if is_integer(Max), Max >= 0, ODBCType /= mssql -> + [<<" limit ">>, jlib:integer_to_binary(Max+1)]; + true -> + [] + end, + TopClause = if is_integer(Max), Max >= 0, ODBCType == mssql -> + [<<" TOP ">>, jlib:integer_to_binary(Max+1)]; + true -> + [] + end, + WithClause = case With of + {text, <<>>} -> + []; + {text, Txt} -> + [<<" and match (txt) against ('">>, + ejabberd_odbc:escape(Txt), <<"')">>]; + {_, _, <<>>} -> + [<<" and bare_peer='">>, + ejabberd_odbc:escape(jid:to_string(With)), + <<"'">>]; + {_, _, _} -> + [<<" and peer='">>, + ejabberd_odbc:escape(jid:to_string(With)), + <<"'">>]; + none -> + [] + end, + PageClause = case catch jlib:binary_to_integer(ID) of + I when is_integer(I), I >= 0 -> + case Direction of + before -> + [<<" AND timestamp < ">>, ID]; + aft -> + [<<" AND timestamp > ">>, ID]; + _ -> + [] + end; + _ -> + [] + end, + StartClause = case Start of + {_, _, _} -> + [<<" and timestamp >= ">>, + jlib:integer_to_binary(now_to_usec(Start))]; + _ -> + [] + end, + EndClause = case End of + {_, _, _} -> + [<<" and timestamp <= ">>, + jlib:integer_to_binary(now_to_usec(End))]; + _ -> + [] + end, + SUser = ejabberd_odbc:escape(User), + + Query = [<<"SELECT ">>, TopClause, <<" timestamp, xml, peer, kind, nick" + " FROM archive WHERE username='">>, + SUser, <<"'">>, WithClause, StartClause, EndClause, + PageClause], + + QueryPage = + case Direction of + before -> + % ID can be empty because of + % XEP-0059: Result Set Management + % 2.5 Requesting the Last Page in a Result Set + [<<"SELECT timestamp, xml, peer, kind, nick FROM (">>, Query, + <<" ORDER BY timestamp DESC ">>, + LimitClause, <<") AS t ORDER BY timestamp ASC;">>]; + _ -> + [Query, <<" ORDER BY timestamp ASC ">>, + LimitClause, <<";">>] + end, + {QueryPage, + [<<"SELECT COUNT(*) FROM archive WHERE username='">>, + SUser, <<"'">>, WithClause, StartClause, EndClause, <<";">>]}. + +update(LServer, Table, Fields, Vals, Where) -> + UPairs = lists:zipwith(fun (A, B) -> + <> + end, + Fields, Vals), + case ejabberd_odbc:sql_query(LServer, + [<<"update ">>, Table, <<" set ">>, + join(UPairs, <<", ">>), <<" where ">>, Where, + <<";">>]) + of + {updated, 1} -> {updated, 1}; + _ -> + ejabberd_odbc:sql_query(LServer, + [<<"insert into ">>, Table, <<"(">>, + join(Fields, <<", ">>), <<") values ('">>, + join(Vals, <<"', '">>), <<"');">>]) + end. + +%% Almost a copy of string:join/2. +join([], _Sep) -> []; +join([H | T], Sep) -> [H, [[Sep, X] || X <- T]]. diff --git a/src/mod_muc.erl b/src/mod_muc.erl index 95631e25c..6aa186318 100644 --- a/src/mod_muc.erl +++ b/src/mod_muc.erl @@ -154,15 +154,7 @@ forget_room(ServerHost, Host, Name) -> remove_room_mam(LServer, Host, Name) -> case gen_mod:is_loaded(LServer, mod_mam) of true -> - U = jid:nodeprep(Name), - S = jid:nameprep(Host), - DBType = gen_mod:db_type(LServer, mod_mam), - if DBType == odbc -> - mod_mam:remove_user(jid:to_string({U, S, <<>>}), - LServer, DBType); - true -> - mod_mam:remove_user(U, S, DBType) - end; + mod_mam:remove_room(LServer, Name, Host); false -> ok end.