summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorEvgeniy Khramtsov <ekhramtsov@process-one.net>2016-04-15 15:12:12 +0300
committerEvgeniy Khramtsov <ekhramtsov@process-one.net>2016-04-15 15:12:12 +0300
commitfb0ecf33612d5de004817824096d950fac5b5cc3 (patch)
treef29c5855c6737aa11ef1f71dee92a998407df89f /src
parentAdd preliminary tests on ACL module and prepare clean-up / refactor (diff)
parentClean mod_mam.erl from DB specific code (diff)
Merge branch 'move-db-code'
Diffstat (limited to 'src')
-rw-r--r--src/gen_mod.erl15
-rw-r--r--src/mod_announce.erl328
-rw-r--r--src/mod_announce_mnesia.erl129
-rw-r--r--src/mod_announce_riak.erl87
-rw-r--r--src/mod_announce_sql.erl132
-rw-r--r--src/mod_blocking.erl222
-rw-r--r--src/mod_blocking_mnesia.erl85
-rw-r--r--src/mod_blocking_riak.erl98
-rw-r--r--src/mod_blocking_sql.erl87
-rw-r--r--src/mod_caps.erl140
-rw-r--r--src/mod_caps_mnesia.erl73
-rw-r--r--src/mod_caps_riak.erl38
-rw-r--r--src/mod_caps_sql.erl71
-rw-r--r--src/mod_irc.erl176
-rw-r--r--src/mod_irc_mnesia.erl69
-rw-r--r--src/mod_irc_riak.erl49
-rw-r--r--src/mod_irc_sql.erl91
-rw-r--r--src/mod_last.erl152
-rw-r--r--src/mod_last_mnesia.erl72
-rw-r--r--src/mod_last_riak.erl53
-rw-r--r--src/mod_last_sql.erl75
-rw-r--r--src/mod_mam.erl541
-rw-r--r--src/mod_mam_mnesia.erl178
-rw-r--r--src/mod_mam_sql.erl309
-rw-r--r--src/mod_muc.erl457
-rw-r--r--src/mod_muc_mnesia.erl163
-rw-r--r--src/mod_muc_riak.erl117
-rw-r--r--src/mod_muc_sql.erl202
-rw-r--r--src/mod_offline.erl936
-rw-r--r--src/mod_offline_mnesia.erl232
-rw-r--r--src/mod_offline_riak.erl153
-rw-r--r--src/mod_offline_sql.erl252
-rw-r--r--src/mod_privacy.erl721
-rw-r--r--src/mod_privacy_mnesia.erl198
-rw-r--r--src/mod_privacy_riak.erl160
-rw-r--r--src/mod_privacy_sql.erl397
-rw-r--r--src/mod_private.erl217
-rw-r--r--src/mod_private_mnesia.erl97
-rw-r--r--src/mod_private_riak.erl67
-rw-r--r--src/mod_private_sql.erl97
-rw-r--r--src/mod_roster.erl653
-rw-r--r--src/mod_roster_mnesia.erl171
-rw-r--r--src/mod_roster_riak.erl113
-rw-r--r--src/mod_roster_sql.erl308
-rw-r--r--src/mod_shared_roster.erl500
-rw-r--r--src/mod_shared_roster_mnesia.erl167
-rw-r--r--src/mod_shared_roster_riak.erl139
-rw-r--r--src/mod_shared_roster_sql.erl212
-rw-r--r--src/mod_vcard.erl698
-rw-r--r--src/mod_vcard_mnesia.erl213
-rw-r--r--src/mod_vcard_riak.erl151
-rw-r--r--src/mod_vcard_sql.erl268
-rw-r--r--src/mod_vcard_xupdate.erl145
-rw-r--r--src/mod_vcard_xupdate_mnesia.erl69
-rw-r--r--src/mod_vcard_xupdate_riak.erl44
-rw-r--r--src/mod_vcard_xupdate_sql.erl79
56 files changed, 6481 insertions, 5185 deletions
diff --git a/src/gen_mod.erl b/src/gen_mod.erl
index 1044d095..a290aa20 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) ->
diff --git a/src/mod_announce.erl b/src/mod_announce.erl
index 0cf8c534..d7251c50 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.
-
-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;
- _ ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:delete_motd(LServer).
+
+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}].
-
-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.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
+
+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 00000000..c43eb853
--- /dev/null
+++ b/src/mod_announce_mnesia.erl
@@ -0,0 +1,129 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..7ced0b3c
--- /dev/null
+++ b/src/mod_announce_riak.erl
@@ -0,0 +1,87 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..cf10ffd8
--- /dev/null
+++ b/src/mod_announce_sql.erl
@@ -0,0 +1,132 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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
+%%%===================================================================
diff --git a/src/mod_blocking.erl b/src/mod_blocking.erl
index d94b3090..af06e650 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 00000000..5a4bde64
--- /dev/null
+++ b/src/mod_blocking_mnesia.erl
@@ -0,0 +1,85 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..5dd5cfa9
--- /dev/null
+++ b/src/mod_blocking_riak.erl
@@ -0,0 +1,98 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..8177adae
--- /dev/null
+++ b/src/mod_blocking_sql.erl
@@ -0,0 +1,87 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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
+%%%===================================================================
diff --git a/src/mod_caps.erl b/src/mod_caps.erl
index 0646d381..bd0f14f5 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 00000000..0bf04b2c
--- /dev/null
+++ b/src/mod_caps_mnesia.erl
@@ -0,0 +1,73 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..6e59ba86
--- /dev/null
+++ b/src/mod_caps_riak.erl
@@ -0,0 +1,38 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..353b95b1
--- /dev/null
+++ b/src/mod_caps_sql.erl
@@ -0,0 +1,71 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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]].
+
diff --git a/src/mod_irc.erl b/src/mod_irc.erl
index f6e452d8..e0c658de 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{}}.
-
-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.
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(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) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-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 00000000..9f8117ad
--- /dev/null
+++ b/src/mod_irc_mnesia.erl
@@ -0,0 +1,69 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..6ac7befd
--- /dev/null
+++ b/src/mod_irc_riak.erl
@@ -0,0 +1,49 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..bf6dbb48
--- /dev/null
+++ b/src/mod_irc_sql.erl
@@ -0,0 +1,91 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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
+%%%===================================================================
diff --git a/src/mod_last.erl b/src/mod_last.erl
index 009a1cb0..1af1847b 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).
-
-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{}}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(LUser, LServer).
-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 00000000..7a1610ab
--- /dev/null
+++ b/src/mod_last_mnesia.erl
@@ -0,0 +1,72 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..d25a3a15
--- /dev/null
+++ b/src/mod_last_riak.erl
@@ -0,0 +1,53 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..edfa3763
--- /dev/null
+++ b/src/mod_last_sql.erl
@@ -0,0 +1,75 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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
+%%%===================================================================
diff --git a/src/mod_mam.erl b/src/mod_mam.erl
index 862adee9..098ee896 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) ->
- <<A/binary, "='", B/binary, "'">>
- 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 00000000..007ef5eb
--- /dev/null
+++ b/src/mod_mam_mnesia.erl
@@ -0,0 +1,178 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..1f24de31
--- /dev/null
+++ b/src/mod_mam_sql.erl
@@ -0,0 +1,309 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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) ->
+ <<A/binary, "='", B/binary, "'">>
+ 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 9d4b4985..6aa18631 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,96 +137,24 @@ 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
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.
@@ -233,48 +173,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 +183,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 +534,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 +725,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 +765,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 +912,17 @@ opts_to_binary(Opts) ->
Opt
end, Opts).
-update_tables(Host) ->
- update_muc_room_table(Host),
- update_muc_registered_table(Host).
-
-muc_room_schema() ->
- {record_info(fields, muc_room), #muc_room{}}.
-
-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.
+export(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:export(LServer).
-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.
+import(LServer) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-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 00000000..e3ae3697
--- /dev/null
+++ b/src/mod_muc_mnesia.erl
@@ -0,0 +1,163 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..bc6e5959
--- /dev/null
+++ b/src/mod_muc_riak.erl
@@ -0,0 +1,117 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..9acd9f8d
--- /dev/null
+++ b/src/mod_muc_sql.erl
@@ -0,0 +1,202 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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
+%%%===================================================================
diff --git a/src/mod_offline.erl b/src/mod_offline.erl
index 54eda165..2cdd82ae 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, <<Seq:20/binary, "+", From_s/binary>>) ->
- 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, <<Seq:20/binary, "+", From_s/binary>>) ->
- 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),
- {<<TS/binary, "+", From_s/binary>>, 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).
-
-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),
- [{<<TS/binary, "+", From/binary>>, 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.
+ 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)).
-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},
- 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) ->
+user_queue_parse_query(LUser, LServer, Query) ->
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
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}].
-
-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.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
+
+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 00000000..6a1d9e30
--- /dev/null
+++ b/src/mod_offline_mnesia.erl
@@ -0,0 +1,232 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..217e8f82
--- /dev/null
+++ b/src/mod_offline_riak.erl
@@ -0,0 +1,153 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..37b90163
--- /dev/null
+++ b/src/mod_offline_sql.erl
@@ -0,0 +1,252 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 15 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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.
diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl
index ea36fed2..413dcb52 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 00000000..4026b7f6
--- /dev/null
+++ b/src/mod_privacy_mnesia.erl
@@ -0,0 +1,198 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..0c43e74f
--- /dev/null
+++ b/src/mod_privacy_riak.erl
@@ -0,0 +1,160 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..ffaf5518
--- /dev/null
+++ b/src/mod_privacy_sql.erl
@@ -0,0 +1,397 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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).
diff --git a/src/mod_private.erl b/src/mod_private.erl
index b4b06450..029789e6 100644
--- a/src/mod_private.erl
+++ b/src/mod_private.erl
@@ -39,12 +39,14 @@
-include("logger.hrl").
-include("jlib.hrl").
-
--record(private_storage,
- {usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() |
- '$1' | '_'},
- xml = #xmlel{} :: xmlel() | '_' | '$1'}).
-
+-include("mod_private.hrl").
+
+-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)).
-
-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})}.
+ Mod = gen_mod:db_mod(Server, ?MODULE),
+ Mod:remove_user(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}].
-
-import(_LServer, mnesia, #private_storage{} = PS) ->
- mnesia:dirty_write(PS);
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
-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 00000000..7a852c4f
--- /dev/null
+++ b/src/mod_private_mnesia.erl
@@ -0,0 +1,97 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..11cfa477
--- /dev/null
+++ b/src/mod_private_riak.erl
@@ -0,0 +1,67 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..1b77c48b
--- /dev/null
+++ b/src/mod_private_sql.erl
@@ -0,0 +1,97 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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
+%%%===================================================================
diff --git a/src/mod_roster.erl b/src/mod_roster.erl
index 35072e5f..16354dd8 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)
- 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)
+ 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, [], 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}].
-
-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.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
+
+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 00000000..ddfa34d6
--- /dev/null
+++ b/src/mod_roster_mnesia.erl
@@ -0,0 +1,171 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..38e87382
--- /dev/null
+++ b/src/mod_roster_riak.erl
@@ -0,0 +1,113 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..82662865
--- /dev/null
+++ b/src/mod_roster_sql.erl
@@ -0,0 +1,308 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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).
diff --git a/src/mod_shared_roster.erl b/src/mod_shared_roster.erl
index 212a7d47..6670cf77 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'}).
-
--record(sr_user, {us = {<<"">>, <<"">>} :: {binary(), binary()},
- group_host = {<<"">>, <<"">>} :: {binary(), binary()}}).
+-include("mod_shared_roster.hrl").
+
+-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}].
-
-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.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
+
+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 00000000..ca2e55e2
--- /dev/null
+++ b/src/mod_shared_roster_mnesia.erl
@@ -0,0 +1,167 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..0df35e37
--- /dev/null
+++ b/src/mod_shared_roster_riak.erl
@@ -0,0 +1,139 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..21ea768a
--- /dev/null
+++ b/src/mod_shared_roster_sql.erl
@@ -0,0 +1,212 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 14 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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).
diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl
index 4d7c80d5..e5f5d9e3 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)).
-
-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.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:get_vcard(LUser, LServer).
-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) -> <<S/binary, $.>> 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)).
-
-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})}.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:remove_user(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 00000000..781a135c
--- /dev/null
+++ b/src/mod_vcard_mnesia.erl
@@ -0,0 +1,213 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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) -> <<S/binary, $.>> end, Parts)),
+ right, $.).
diff --git a/src/mod_vcard_riak.erl b/src/mod_vcard_riak.erl
new file mode 100644
index 00000000..38634738
--- /dev/null
+++ b/src/mod_vcard_riak.erl
@@ -0,0 +1,151 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..fff39091
--- /dev/null
+++ b/src/mod_vcard_sql.erl
@@ -0,0 +1,268 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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.
diff --git a/src/mod_vcard_xupdate.erl b/src/mod_vcard_xupdate.erl
index 18fb09a5..198312c3 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}].
-
-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.
+ Mod = gen_mod:db_mod(LServer, ?MODULE),
+ Mod:import(LServer).
+
+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 00000000..f1b1693e
--- /dev/null
+++ b/src/mod_vcard_xupdate_mnesia.erl
@@ -0,0 +1,69 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..129a0c6a
--- /dev/null
+++ b/src/mod_vcard_xupdate_riak.erl
@@ -0,0 +1,44 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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 00000000..d580b151
--- /dev/null
+++ b/src/mod_vcard_xupdate_sql.erl
@@ -0,0 +1,79 @@
+%%%-------------------------------------------------------------------
+%%% @author Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%% @copyright (C) 2016, Evgeny Khramtsov
+%%% @doc
+%%%
+%%% @end
+%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
+%%%-------------------------------------------------------------------
+-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
+%%%===================================================================