Merge branch 'move-db-code'

This commit is contained in:
Evgeniy Khramtsov 2016-04-15 15:12:12 +03:00
commit fb0ecf3361
64 changed files with 6519 additions and 5177 deletions

5
include/mod_announce.hrl Normal file
View File

@ -0,0 +1,5 @@
-record(motd, {server = <<"">> :: binary(),
packet = #xmlel{} :: xmlel()}).
-record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
dummy = [] :: [] | '_'}).

4
include/mod_caps.hrl Normal file
View File

@ -0,0 +1,4 @@
-record(caps_features,
{node_pair = {<<"">>, <<"">>} :: {binary(), binary()},
features = [] :: [binary()] | pos_integer()
}).

15
include/mod_irc.hrl Normal file
View File

@ -0,0 +1,15 @@
-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
{binary(), binary(), inet:port_number()} |
{binary(), binary()} |
{binary()}.
-type irc_data() :: [{username, binary()} | {connections_params, [conn_param()]}].
-record(irc_connection,
{jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()},
pid = self() :: pid()}).
-record(irc_custom,
{us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()},
binary()},
data = [] :: irc_data()}).

3
include/mod_last.hrl Normal file
View File

@ -0,0 +1,3 @@
-record(last_activity, {us = {<<"">>, <<"">>} :: {binary(), binary()},
timestamp = 0 :: non_neg_integer(),
status = <<"">> :: binary()}).

4
include/mod_private.hrl Normal file
View File

@ -0,0 +1,4 @@
-record(private_storage,
{usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() |
'$1' | '_'},
xml = #xmlel{} :: xmlel() | '_' | '$1'}).

View File

@ -0,0 +1,5 @@
-record(sr_group, {group_host = {<<"">>, <<"">>} :: {'$1' | binary(), '$2' | binary()},
opts = [] :: list() | '_' | '$2'}).
-record(sr_user, {us = {<<"">>, <<"">>} :: {binary(), binary()},
group_host = {<<"">>, <<"">>} :: {binary(), binary()}}).

8
include/mod_vcard.hrl Normal file
View File

@ -0,0 +1,8 @@
-record(vcard_search,
{us, user, luser, fn, lfn, family, lfamily, given,
lgiven, middle, lmiddle, nickname, lnickname, bday,
lbday, ctry, lctry, locality, llocality, email, lemail,
orgname, lorgname, orgunit, lorgunit}).
-record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()} | binary(),
vcard = #xmlel{} :: xmlel()}).

View File

@ -0,0 +1,2 @@
-record(vcard_xupdate, {us = {<<>>, <<>>} :: {binary(), binary()},
hash = <<>> :: binary()}).

View File

@ -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) ->

View File

@ -41,11 +41,16 @@
-include("logger.hrl").
-include("jlib.hrl").
-include("adhoc.hrl").
-include("mod_announce.hrl").
-record(motd, {server = <<"">> :: binary(),
packet = #xmlel{} :: xmlel()}).
-record(motd_users, {us = {<<"">>, <<"">>} :: {binary(), binary()} | '$1',
dummy = [] :: [] | '_'}).
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #motd{} | #motd_users{}) -> ok | pass.
-callback set_motd_users(binary(), [{binary(), binary(), binary()}]) -> {atomic, any()}.
-callback set_motd(binary(), xmlel()) -> {atomic, any()}.
-callback delete_motd(binary()) -> {atomic, any()}.
-callback get_motd(binary()) -> {ok, xmlel()} | error.
-callback is_motd_user(binary(), binary()) -> boolean().
-callback set_motd_user(binary(), binary()) -> {atomic, any()}.
-define(PROCNAME, ejabberd_announce).
@ -55,20 +60,8 @@
tokenize(Node) -> str:tokens(Node, <<"/#">>).
start(Host, Opts) ->
case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(motd,
[{disc_copies, [node()]},
{attributes,
record_info(fields, motd)}]),
mnesia:create_table(motd_users,
[{disc_copies, [node()]},
{attributes,
record_info(fields, motd_users)}]),
update_tables();
_ ->
ok
end,
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
ejabberd_hooks:add(local_send_to_resource_hook, Host,
?MODULE, announce, 50),
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE, disco_identity, 50),
@ -789,41 +782,8 @@ announce_motd(Host, Packet) ->
announce_motd_update(LServer, Packet),
Sessions = ejabberd_sm:get_vh_session_list(LServer),
announce_online1(Sessions, LServer, Packet),
case gen_mod:db_type(LServer, ?MODULE) of
mnesia ->
F = fun() ->
lists:foreach(
fun({U, S, _R}) ->
mnesia:write(#motd_users{us = {U, S}})
end, Sessions)
end,
mnesia:transaction(F);
riak ->
try
lists:foreach(
fun({U, S, _R}) ->
ok = ejabberd_riak:put(#motd_users{us = {U, S}},
motd_users_schema(),
[{'2i', [{<<"server">>, S}]}])
end, Sessions),
{atomic, ok}
catch _:{badmatch, Err} ->
{atomic, Err}
end;
odbc ->
F = fun() ->
lists:foreach(
fun({U, _S, _R}) ->
Username = ejabberd_odbc:escape(U),
odbc_queries:update_t(
<<"motd">>,
[<<"username">>, <<"xml">>],
[Username, <<"">>],
[<<"username='">>, Username, <<"'">>])
end, Sessions)
end,
ejabberd_odbc:sql_transaction(LServer, F)
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_motd_users(LServer, Sessions).
announce_motd_update(From, To, Packet) ->
Host = To#jid.lserver,
@ -853,27 +813,8 @@ announce_all_hosts_motd_update(From, To, Packet) ->
announce_motd_update(LServer, Packet) ->
announce_motd_delete(LServer),
case gen_mod:db_type(LServer, ?MODULE) of
mnesia ->
F = fun() ->
mnesia:write(#motd{server = LServer, packet = Packet})
end,
mnesia:transaction(F);
riak ->
{atomic, ejabberd_riak:put(#motd{server = LServer,
packet = Packet},
motd_schema())};
odbc ->
XML = ejabberd_odbc:escape(fxml:element_to_binary(Packet)),
F = fun() ->
odbc_queries:update_t(
<<"motd">>,
[<<"username">>, <<"xml">>],
[<<"">>, XML],
[<<"username=''">>])
end,
ejabberd_odbc:sql_transaction(LServer, F)
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_motd(LServer, Packet).
announce_motd_delete(From, To, Packet) ->
Host = To#jid.lserver,
@ -902,112 +843,30 @@ announce_all_hosts_motd_delete(From, To, Packet) ->
end.
announce_motd_delete(LServer) ->
case gen_mod:db_type(LServer, ?MODULE) of
mnesia ->
F = fun() ->
mnesia:delete({motd, LServer}),
mnesia:write_lock_table(motd_users),
Users = mnesia:select(
motd_users,
[{#motd_users{us = '$1', _ = '_'},
[{'==', {element, 2, '$1'}, LServer}],
['$1']}]),
lists:foreach(fun(US) ->
mnesia:delete({motd_users, US})
end, Users)
end,
mnesia:transaction(F);
riak ->
try
ok = ejabberd_riak:delete(motd, LServer),
ok = ejabberd_riak:delete_by_index(motd_users,
<<"server">>,
LServer),
{atomic, ok}
catch _:{badmatch, Err} ->
{atomic, Err}
end;
odbc ->
F = fun() ->
ejabberd_odbc:sql_query_t([<<"delete from motd;">>])
end,
ejabberd_odbc:sql_transaction(LServer, F)
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:delete_motd(LServer).
send_motd(JID) ->
send_motd(JID, gen_mod:db_type(JID#jid.lserver, ?MODULE)).
send_motd(#jid{luser = LUser, lserver = LServer} = JID, mnesia) ->
case catch mnesia:dirty_read({motd, LServer}) of
[#motd{packet = Packet}] ->
US = {LUser, LServer},
case catch mnesia:dirty_read({motd_users, US}) of
[#motd_users{}] ->
ok;
_ ->
send_motd(#jid{luser = LUser, lserver = LServer} = JID) when LUser /= <<>> ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:get_motd(LServer) of
{ok, Packet} ->
case Mod:is_motd_user(LUser, LServer) of
false ->
Local = jid:make(<<>>, LServer, <<>>),
ejabberd_router:route(Local, JID, Packet),
F = fun() ->
mnesia:write(#motd_users{us = US})
end,
mnesia:transaction(F)
Mod:set_motd_user(LUser, LServer);
true ->
ok
end;
_ ->
error ->
ok
end;
send_motd(#jid{luser = LUser, lserver = LServer} = JID, riak) ->
case catch ejabberd_riak:get(motd, motd_schema(), LServer) of
{ok, #motd{packet = Packet}} ->
US = {LUser, LServer},
case ejabberd_riak:get(motd_users, motd_users_schema(), US) of
{ok, #motd_users{}} ->
ok;
_ ->
Local = jid:make(<<>>, LServer, <<>>),
ejabberd_router:route(Local, JID, Packet),
{atomic, ejabberd_riak:put(
#motd_users{us = US}, motd_users_schema(),
[{'2i', [{<<"server">>, LServer}]}])}
end;
_ ->
ok
end;
send_motd(#jid{luser = LUser, lserver = LServer} = JID, odbc) when LUser /= <<>> ->
case catch ejabberd_odbc:sql_query(
LServer, [<<"select xml from motd where username='';">>]) of
{selected, [<<"xml">>], [[XML]]} ->
case fxml_stream:parse_element(XML) of
{error, _} ->
ok;
Packet ->
Username = ejabberd_odbc:escape(LUser),
case catch ejabberd_odbc:sql_query(
LServer,
[<<"select username from motd "
"where username='">>, Username, <<"';">>]) of
{selected, [<<"username">>], []} ->
Local = jid:make(<<"">>, LServer, <<"">>),
ejabberd_router:route(Local, JID, Packet),
F = fun() ->
odbc_queries:update_t(
<<"motd">>,
[<<"username">>, <<"xml">>],
[Username, <<"">>],
[<<"username='">>, Username, <<"'">>])
end,
ejabberd_odbc:sql_transaction(LServer, F);
_ ->
ok
end
end;
_ ->
ok
end;
send_motd(_, odbc) ->
send_motd(_) ->
ok.
get_stored_motd(LServer) ->
case get_stored_motd_packet(LServer, gen_mod:db_type(LServer, ?MODULE)) of
Mod = gen_mod:db_mod(LServer, ?MODULE),
case Mod:get_motd(LServer) of
{ok, Packet} ->
{fxml:get_subtag_cdata(Packet, <<"subject">>),
fxml:get_subtag_cdata(Packet, <<"body">>)};
@ -1015,34 +874,6 @@ get_stored_motd(LServer) ->
{<<>>, <<>>}
end.
get_stored_motd_packet(LServer, mnesia) ->
case catch mnesia:dirty_read({motd, LServer}) of
[#motd{packet = Packet}] ->
{ok, Packet};
_ ->
error
end;
get_stored_motd_packet(LServer, riak) ->
case ejabberd_riak:get(motd, motd_schema(), LServer) of
{ok, #motd{packet = Packet}} ->
{ok, Packet};
_ ->
error
end;
get_stored_motd_packet(LServer, odbc) ->
case catch ejabberd_odbc:sql_query(
LServer, [<<"select xml from motd where username='';">>]) of
{selected, [<<"xml">>], [[XML]]} ->
case fxml_stream:parse_element(XML) of
{error, _} ->
error;
Packet ->
{ok, Packet}
end;
_ ->
error
end.
%% This function is similar to others, but doesn't perform any ACL verification
send_announcement_to_all(Host, SubjectS, BodyS) ->
SubjectEls = if SubjectS /= <<>> ->
@ -1076,96 +907,17 @@ get_access(Host) ->
none).
%%-------------------------------------------------------------------------
update_tables() ->
update_motd_table(),
update_motd_users_table().
update_motd_table() ->
Fields = record_info(fields, motd),
case mnesia:table_info(motd, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
motd, Fields, set,
fun(#motd{server = S}) -> S end,
fun(#motd{server = S, packet = P} = R) ->
NewS = iolist_to_binary(S),
NewP = fxml:to_xmlel(P),
R#motd{server = NewS, packet = NewP}
end);
_ ->
?INFO_MSG("Recreating motd table", []),
mnesia:transform_table(motd, ignore, Fields)
end.
update_motd_users_table() ->
Fields = record_info(fields, motd_users),
case mnesia:table_info(motd_users, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
motd_users, Fields, set,
fun(#motd_users{us = {U, _}}) -> U end,
fun(#motd_users{us = {U, S}} = R) ->
NewUS = {iolist_to_binary(U),
iolist_to_binary(S)},
R#motd_users{us = NewUS}
end);
_ ->
?INFO_MSG("Recreating motd_users table", []),
mnesia:transform_table(motd_users, ignore, Fields)
end.
motd_schema() ->
{record_info(fields, motd), #motd{}}.
motd_users_schema() ->
{record_info(fields, motd_users), #motd_users{}}.
export(_Server) ->
[{motd,
fun(Host, #motd{server = LServer, packet = El})
when LServer == Host ->
[[<<"delete from motd where username='';">>],
[<<"insert into motd(username, xml) values ('', '">>,
ejabberd_odbc:escape(fxml:element_to_binary(El)),
<<"');">>]];
(_Host, _R) ->
[]
end},
{motd_users,
fun(Host, #motd_users{us = {LUser, LServer}})
when LServer == Host, LUser /= <<"">> ->
Username = ejabberd_odbc:escape(LUser),
[[<<"delete from motd where username='">>, Username, <<"';">>],
[<<"insert into motd(username, xml) values ('">>,
Username, <<"', '');">>]];
(_Host, _R) ->
[]
end}].
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
import(LServer) ->
[{<<"select xml from motd where username='';">>,
fun([XML]) ->
El = fxml_stream:parse_element(XML),
#motd{server = LServer, packet = El}
end},
{<<"select username from motd where xml='';">>,
fun([LUser]) ->
#motd_users{us = {LUser, LServer}}
end}].
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:import(LServer).
import(_LServer, mnesia, #motd{} = Motd) ->
mnesia:dirty_write(Motd);
import(_LServer, mnesia, #motd_users{} = Users) ->
mnesia:dirty_write(Users);
import(_LServer, riak, #motd{} = Motd) ->
ejabberd_riak:put(Motd, motd_schema());
import(_LServer, riak, #motd_users{us = {_, S}} = Users) ->
ejabberd_riak:put(Users, motd_users_schema(),
[{'2i', [{<<"server">>, S}]}]);
import(_, _, _) ->
pass.
import(LServer, DBType, LA) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, LA).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;

129
src/mod_announce_mnesia.erl Normal file
View File

@ -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.

87
src/mod_announce_riak.erl Normal file
View File

@ -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{}}.

132
src/mod_announce_sql.erl Normal file
View File

@ -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
%%%===================================================================

View File

@ -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].

View File

@ -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
%%%===================================================================

98
src/mod_blocking_riak.erl Normal file
View File

@ -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
%%%===================================================================

87
src/mod_blocking_sql.erl Normal file
View File

@ -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
%%%===================================================================

View File

@ -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">>,

73
src/mod_caps_mnesia.erl Normal file
View File

@ -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.

38
src/mod_caps_riak.erl Normal file
View File

@ -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{}}.

71
src/mod_caps_sql.erl Normal file
View File

@ -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]].

View File

@ -33,7 +33,8 @@
%% API
-export([start_link/2, start/2, stop/1, export/1, import/1,
import/3, closed_connection/3, get_connection_params/3]).
import/3, closed_connection/3, get_connection_params/3,
data_to_binary/2]).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3,
@ -46,6 +47,8 @@
-include("adhoc.hrl").
-include("mod_irc.hrl").
-define(DEFAULT_IRC_ENCODING, <<"iso8859-15">>).
-define(DEFAULT_IRC_PORT, 6667).
@ -58,27 +61,19 @@
[<<"koi8-r">>, <<"iso8859-15">>, <<"iso8859-1">>, <<"iso8859-2">>,
<<"utf-8">>, <<"utf-8+latin-1">>]).
-type conn_param() :: {binary(), binary(), inet:port_number(), binary()} |
{binary(), binary(), inet:port_number()} |
{binary(), binary()} |
{binary()}.
-record(irc_connection,
{jid_server_host = {#jid{}, <<"">>, <<"">>} :: {jid(), binary(), binary()},
pid = self() :: pid()}).
-record(irc_custom,
{us_host = {{<<"">>, <<"">>}, <<"">>} :: {{binary(), binary()},
binary()},
data = [] :: [{username, binary()} |
{connections_params, [conn_param()]}]}).
-record(state, {host = <<"">> :: binary(),
server_host = <<"">> :: binary(),
access = all :: atom()}).
-define(PROCNAME, ejabberd_mod_irc).
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #irc_custom{}) -> ok | pass.
-callback get_data(binary(), binary(), {binary(), binary()}) ->
error | empty | irc_data().
-callback set_data(binary(), binary(), {binary(), binary()}, irc_data()) ->
{atomic, any()}.
%%====================================================================
%% API
%%====================================================================
@ -119,14 +114,8 @@ init([Host, Opts]) ->
ejabberd:start_app(iconv),
MyHost = gen_mod:get_opt_host(Host, Opts,
<<"irc.@HOST@">>),
case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(irc_custom,
[{disc_copies, [node()]},
{attributes, record_info(fields, irc_custom)}]),
update_table();
_ -> ok
end,
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
Access = gen_mod:get_opt(access, Opts,
fun(A) when is_atom(A) -> A end,
all),
@ -597,43 +586,8 @@ process_irc_register(ServerHost, Host, From, _To,
get_data(ServerHost, Host, From) ->
LServer = jid:nameprep(ServerHost),
get_data(LServer, Host, From,
gen_mod:db_type(LServer, ?MODULE)).
get_data(_LServer, Host, From, mnesia) ->
#jid{luser = LUser, lserver = LServer} = From,
US = {LUser, LServer},
case catch mnesia:dirty_read({irc_custom, {US, Host}})
of
{'EXIT', _Reason} -> error;
[] -> empty;
[#irc_custom{data = Data}] -> Data
end;
get_data(LServer, Host, From, riak) ->
#jid{luser = LUser, lserver = LServer} = From,
US = {LUser, LServer},
case ejabberd_riak:get(irc_custom, irc_custom_schema(), {US, Host}) of
{ok, #irc_custom{data = Data}} ->
Data;
{error, notfound} ->
empty;
_Err ->
error
end;
get_data(LServer, Host, From, odbc) ->
SJID =
ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
SHost = ejabberd_odbc:escape(Host),
case catch ejabberd_odbc:sql_query(LServer,
[<<"select data from irc_custom where jid='">>,
SJID, <<"' and host='">>, SHost,
<<"';">>])
of
{selected, [<<"data">>], [[SData]]} ->
data_to_binary(From, ejabberd_odbc:decode_term(SData));
{'EXIT', _} -> error;
{selected, _, _} -> empty
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_data(LServer, Host, From).
get_form(ServerHost, Host, From, [], Lang) ->
#jid{user = User, server = Server} = From,
@ -743,37 +697,8 @@ get_form(_ServerHost, _Host, _, _, _Lang) ->
set_data(ServerHost, Host, From, Data) ->
LServer = jid:nameprep(ServerHost),
set_data(LServer, Host, From, data_to_binary(From, Data),
gen_mod:db_type(LServer, ?MODULE)).
set_data(_LServer, Host, From, Data, mnesia) ->
{LUser, LServer, _} = jid:tolower(From),
US = {LUser, LServer},
F = fun () ->
mnesia:write(#irc_custom{us_host = {US, Host},
data = Data})
end,
mnesia:transaction(F);
set_data(LServer, Host, From, Data, riak) ->
{LUser, LServer, _} = jid:tolower(From),
US = {LUser, LServer},
{atomic, ejabberd_riak:put(#irc_custom{us_host = {US, Host},
data = Data},
irc_custom_schema())};
set_data(LServer, Host, From, Data, odbc) ->
SJID =
ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:remove_resource(From)))),
SHost = ejabberd_odbc:escape(Host),
SData = ejabberd_odbc:encode_term(Data),
F = fun () ->
odbc_queries:update_t(<<"irc_custom">>,
[<<"jid">>, <<"host">>, <<"data">>],
[SJID, SHost, SData],
[<<"jid='">>, SJID, <<"' and host='">>,
SHost, <<"'">>]),
ok
end,
ejabberd_odbc:sql_transaction(LServer, F).
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_data(LServer, Host, From, data_to_binary(From, Data)).
set_form(ServerHost, Host, From, [], Lang, XData) ->
case {lists:keysearch(<<"username">>, 1, XData),
@ -1314,66 +1239,17 @@ conn_params_to_list(Params) ->
Port, binary_to_list(P)}
end, Params).
irc_custom_schema() ->
{record_info(fields, irc_custom), #irc_custom{}}.
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
update_table() ->
Fields = record_info(fields, irc_custom),
case mnesia:table_info(irc_custom, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
irc_custom, Fields, set,
fun(#irc_custom{us_host = {_, H}}) -> H end,
fun(#irc_custom{us_host = {{U, S}, H},
data = Data} = R) ->
JID = jid:make(U, S, <<"">>),
R#irc_custom{us_host = {{iolist_to_binary(U),
iolist_to_binary(S)},
iolist_to_binary(H)},
data = data_to_binary(JID, Data)}
end);
_ ->
?INFO_MSG("Recreating irc_custom table", []),
mnesia:transform_table(irc_custom, ignore, Fields)
end.
import(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:import(LServer).
export(_Server) ->
[{irc_custom,
fun(Host, #irc_custom{us_host = {{U, S}, IRCHost},
data = Data}) ->
case str:suffix(Host, IRCHost) of
true ->
SJID = ejabberd_odbc:escape(
jid:to_string(
jid:make(U, S, <<"">>))),
SIRCHost = ejabberd_odbc:escape(IRCHost),
SData = ejabberd_odbc:encode_term(Data),
[[<<"delete from irc_custom where jid='">>, SJID,
<<"' and host='">>, SIRCHost, <<"';">>],
[<<"insert into irc_custom(jid, host, "
"data) values ('">>,
SJID, <<"', '">>, SIRCHost, <<"', '">>, SData,
<<"');">>]];
false ->
[]
end
end}].
import(_LServer) ->
[{<<"select jid, host, data from irc_custom;">>,
fun([SJID, IRCHost, SData]) ->
#jid{luser = U, lserver = S} = jid:from_string(SJID),
Data = ejabberd_odbc:decode_term(SData),
#irc_custom{us_host = {{U, S}, IRCHost},
data = Data}
end}].
import(_LServer, mnesia, #irc_custom{} = R) ->
mnesia:dirty_write(R);
import(_LServer, riak, #irc_custom{} = R) ->
ejabberd_riak:put(R, irc_custom_schema());
import(_, _, _) ->
pass.
import(LServer, DBType, Data) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, Data).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;

69
src/mod_irc_mnesia.erl Normal file
View File

@ -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.

49
src/mod_irc_riak.erl Normal file
View File

@ -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{}}.

91
src/mod_irc_sql.erl Normal file
View File

@ -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
%%%===================================================================

View File

@ -45,23 +45,21 @@
-include("jlib.hrl").
-include("mod_privacy.hrl").
-include("mod_last.hrl").
-record(last_activity, {us = {<<"">>, <<"">>} :: {binary(), binary()},
timestamp = 0 :: non_neg_integer(),
status = <<"">> :: binary()}).
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #last_activity{}) -> ok | pass.
-callback get_last(binary(), binary()) ->
{ok, non_neg_integer(), binary()} | not_found | {error, any()}.
-callback store_last_info(binary(), binary(), non_neg_integer(), binary()) ->
{atomic, any()}.
-callback remove_user(binary(), binary()) -> {atomic, any()}.
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(last_activity,
[{disc_copies, [node()]},
{attributes,
record_info(fields, last_activity)}]),
update_table();
_ -> ok
end,
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_LAST, ?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
@ -163,38 +161,8 @@ process_sm_iq(From, To,
%% @spec (LUser::string(), LServer::string()) ->
%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
get_last(LUser, LServer) ->
get_last(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
get_last(LUser, LServer, mnesia) ->
case catch mnesia:dirty_read(last_activity,
{LUser, LServer})
of
{'EXIT', Reason} -> {error, Reason};
[] -> not_found;
[#last_activity{timestamp = TimeStamp,
status = Status}] ->
{ok, TimeStamp, Status}
end;
get_last(LUser, LServer, riak) ->
case ejabberd_riak:get(last_activity, last_activity_schema(),
{LUser, LServer}) of
{ok, #last_activity{timestamp = TimeStamp,
status = Status}} ->
{ok, TimeStamp, Status};
{error, notfound} ->
not_found;
Err ->
Err
end;
get_last(LUser, LServer, odbc) ->
case catch odbc_queries:get_last(LServer, LUser) of
{selected, []} ->
not_found;
{selected, [{TimeStamp, Status}]} ->
{ok, TimeStamp, Status};
Reason -> {error, {invalid_result, Reason}}
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_last(LUser, LServer).
get_last_iq(#iq{lang = Lang} = IQ, SubEl, LUser, LServer) ->
case ejabberd_sm:get_user_resources(LUser, LServer) of
@ -237,29 +205,8 @@ on_presence_update(User, Server, _Resource, Status) ->
store_last_info(User, Server, TimeStamp, Status) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
DBType = gen_mod:db_type(LServer, ?MODULE),
store_last_info(LUser, LServer, TimeStamp, Status,
DBType).
store_last_info(LUser, LServer, TimeStamp, Status,
mnesia) ->
US = {LUser, LServer},
F = fun () ->
mnesia:write(#last_activity{us = US,
timestamp = TimeStamp,
status = Status})
end,
mnesia:transaction(F);
store_last_info(LUser, LServer, TimeStamp, Status,
riak) ->
US = {LUser, LServer},
{atomic, ejabberd_riak:put(#last_activity{us = US,
timestamp = TimeStamp,
status = Status},
last_activity_schema())};
store_last_info(LUser, LServer, TimeStamp, Status,
odbc) ->
odbc_queries:set_last_t(LServer, LUser, TimeStamp, Status).
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:store_last_info(LUser, LServer, TimeStamp, Status).
%% @spec (LUser::string(), LServer::string()) ->
%% {ok, TimeStamp::integer(), Status::string()} | not_found
@ -272,71 +219,20 @@ get_last_info(LUser, LServer) ->
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
DBType = gen_mod:db_type(LServer, ?MODULE),
remove_user(LUser, LServer, DBType).
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_user(LUser, LServer).
remove_user(LUser, LServer, mnesia) ->
US = {LUser, LServer},
F = fun () -> mnesia:delete({last_activity, US}) end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
odbc_queries:del_last(LServer, LUser);
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete(last_activity, {LUser, LServer})}.
update_table() ->
Fields = record_info(fields, last_activity),
case mnesia:table_info(last_activity, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
last_activity, Fields, set,
fun(#last_activity{us = {U, _}}) -> U end,
fun(#last_activity{us = {U, S}, status = Status} = R) ->
R#last_activity{us = {iolist_to_binary(U),
iolist_to_binary(S)},
status = iolist_to_binary(Status)}
end);
_ ->
?INFO_MSG("Recreating last_activity table", []),
mnesia:transform_table(last_activity, ignore, Fields)
end.
last_activity_schema() ->
{record_info(fields, last_activity), #last_activity{}}.
export(_Server) ->
[{last_activity,
fun(Host, #last_activity{us = {LUser, LServer},
timestamp = TimeStamp, status = Status})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
Seconds =
ejabberd_odbc:escape(jlib:integer_to_binary(TimeStamp)),
State = ejabberd_odbc:escape(Status),
[[<<"delete from last where username='">>, Username, <<"';">>],
[<<"insert into last(username, seconds, "
"state) values ('">>,
Username, <<"', '">>, Seconds, <<"', '">>, State,
<<"');">>]];
(_Host, _R) ->
[]
end}].
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
import(LServer) ->
[{<<"select username, seconds, state from last">>,
fun([LUser, TimeStamp, State]) ->
#last_activity{us = {LUser, LServer},
timestamp = jlib:binary_to_integer(
TimeStamp),
status = State}
end}].
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:import(LServer).
import(_LServer, mnesia, #last_activity{} = LA) ->
mnesia:dirty_write(LA);
import(_LServer, riak, #last_activity{} = LA) ->
ejabberd_riak:put(LA, last_activity_schema());
import(_, _, _) ->
pass.
import(LServer, DBType, LA) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, LA).
transform_options(Opts) ->
lists:foldl(fun transform_options/2, [], Opts).

72
src/mod_last_mnesia.erl Normal file
View File

@ -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.

53
src/mod_last_riak.erl Normal file
View File

@ -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{}}.

75
src/mod_last_sql.erl Normal file
View File

@ -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
%%%===================================================================

View File

@ -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;

178
src/mod_mam_mnesia.erl Normal file
View File

@ -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}.

309
src/mod_mam_sql.erl Normal file
View File

@ -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]].

View File

@ -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).
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
muc_room_schema() ->
{record_info(fields, muc_room), #muc_room{}}.
import(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:import(LServer).
muc_registered_schema() ->
{record_info(fields, muc_registered), #muc_registered{}}.
update_muc_room_table(_Host) ->
Fields = record_info(fields, muc_room),
case mnesia:table_info(muc_room, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
muc_room, Fields, set,
fun(#muc_room{name_host = {N, _}}) -> N end,
fun(#muc_room{name_host = {N, H},
opts = Opts} = R) ->
R#muc_room{name_host = {iolist_to_binary(N),
iolist_to_binary(H)},
opts = opts_to_binary(Opts)}
end);
_ ->
?INFO_MSG("Recreating muc_room table", []),
mnesia:transform_table(muc_room, ignore, Fields)
end.
update_muc_registered_table(_Host) ->
Fields = record_info(fields, muc_registered),
case mnesia:table_info(muc_registered, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
muc_registered, Fields, set,
fun(#muc_registered{us_host = {_, H}}) -> H end,
fun(#muc_registered{us_host = {{U, S}, H},
nick = Nick} = R) ->
R#muc_registered{us_host = {{iolist_to_binary(U),
iolist_to_binary(S)},
iolist_to_binary(H)},
nick = iolist_to_binary(Nick)}
end);
_ ->
?INFO_MSG("Recreating muc_registered table", []),
mnesia:transform_table(muc_registered, ignore, Fields)
end.
export(_Server) ->
[{muc_room,
fun(Host, #muc_room{name_host = {Name, RoomHost}, opts = Opts}) ->
case str:suffix(Host, RoomHost) of
true ->
SName = ejabberd_odbc:escape(Name),
SRoomHost = ejabberd_odbc:escape(RoomHost),
SOpts = ejabberd_odbc:encode_term(Opts),
[[<<"delete from muc_room where name='">>, SName,
<<"' and host='">>, SRoomHost, <<"';">>],
[<<"insert into muc_room(name, host, opts) ",
"values (">>,
<<"'">>, SName, <<"', '">>, SRoomHost,
<<"', '">>, SOpts, <<"');">>]];
false ->
[]
end
end},
{muc_registered,
fun(Host, #muc_registered{us_host = {{U, S}, RoomHost},
nick = Nick}) ->
case str:suffix(Host, RoomHost) of
true ->
SJID = ejabberd_odbc:escape(
jid:to_string(
jid:make(U, S, <<"">>))),
SNick = ejabberd_odbc:escape(Nick),
SRoomHost = ejabberd_odbc:escape(RoomHost),
[[<<"delete from muc_registered where jid='">>,
SJID, <<"' and host='">>, SRoomHost, <<"';">>],
[<<"insert into muc_registered(jid, host, "
"nick) values ('">>,
SJID, <<"', '">>, SRoomHost, <<"', '">>, SNick,
<<"');">>]];
false ->
[]
end
end}].
import(_LServer) ->
[{<<"select name, host, opts from muc_room;">>,
fun([Name, RoomHost, SOpts]) ->
Opts = opts_to_binary(ejabberd_odbc:decode_term(SOpts)),
#muc_room{name_host = {Name, RoomHost}, opts = Opts}
end},
{<<"select jid, host, nick from muc_registered;">>,
fun([J, RoomHost, Nick]) ->
#jid{user = U, server = S} =
jid:from_string(J),
#muc_registered{us_host = {{U, S}, RoomHost},
nick = Nick}
end}].
import(_LServer, mnesia, #muc_room{} = R) ->
mnesia:dirty_write(R);
import(_LServer, mnesia, #muc_registered{} = R) ->
mnesia:dirty_write(R);
import(_LServer, riak, #muc_room{} = R) ->
ejabberd_riak:put(R, muc_room_schema());
import(_LServer, riak,
#muc_registered{us_host = {_, Host}, nick = Nick} = R) ->
ejabberd_riak:put(R, muc_registered_schema(),
[{'2i', [{<<"nick_host">>, {Nick, Host}}]}]);
import(_, _, _) ->
pass.
import(LServer, DBType, Data) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, Data).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;

163
src/mod_muc_mnesia.erl Normal file
View File

@ -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.

117
src/mod_muc_riak.erl Normal file
View File

@ -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{}}.

202
src/mod_muc_sql.erl Normal file
View File

@ -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
%%%===================================================================

File diff suppressed because it is too large Load Diff

232
src/mod_offline_mnesia.erl Normal file
View File

@ -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.

153
src/mod_offline_riak.erl Normal file
View File

@ -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}.

252
src/mod_offline_sql.erl Normal file
View File

@ -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.

View File

@ -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;

198
src/mod_privacy_mnesia.erl Normal file
View File

@ -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.

160
src/mod_privacy_riak.erl Normal file
View File

@ -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{}}.

397
src/mod_privacy_sql.erl Normal file
View File

@ -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).

View File

@ -39,12 +39,14 @@
-include("logger.hrl").
-include("jlib.hrl").
-include("mod_private.hrl").
-record(private_storage,
{usns = {<<"">>, <<"">>, <<"">>} :: {binary(), binary(), binary() |
'$1' | '_'},
xml = #xmlel{} :: xmlel() | '_' | '$1'}).
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #private_storage{}) -> ok | pass.
-callback set_data(binary(), binary(), [{binary(), xmlel()}]) -> {atomic, any()}.
-callback get_data(binary(), binary(), binary()) -> {ok, xmlel()} | error.
-callback get_all_data(binary(), binary()) -> [xmlel()].
-define(Xmlel_Query(Attrs, Children),
#xmlel{name = <<"query">>, attrs = Attrs,
children = Children}).
@ -52,15 +54,8 @@
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(private_storage,
[{disc_only_copies, [node()]},
{attributes,
record_info(fields, private_storage)}]),
update_table();
_ -> ok
end,
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
@ -139,190 +134,44 @@ filter_xmlels([_ | Xmlels], Data) ->
filter_xmlels(Xmlels, Data).
set_data(LUser, LServer, Data) ->
DBType = gen_mod:db_type(LServer, ?MODULE),
F = fun () ->
lists:foreach(fun (Datum) ->
set_data(LUser, LServer,
Datum, DBType)
end,
Data)
end,
case DBType of
odbc -> ejabberd_odbc:sql_transaction(LServer, F);
mnesia -> mnesia:transaction(F);
riak -> F()
end.
set_data(LUser, LServer, {XmlNS, Xmlel}, mnesia) ->
mnesia:write(#private_storage{usns =
{LUser, LServer, XmlNS},
xml = Xmlel});
set_data(LUser, LServer, {XMLNS, El}, odbc) ->
SData = fxml:element_to_binary(El),
odbc_queries:set_private_data(LServer, LUser, XMLNS, SData);
set_data(LUser, LServer, {XMLNS, El}, riak) ->
ejabberd_riak:put(#private_storage{usns = {LUser, LServer, XMLNS},
xml = El},
private_storage_schema(),
[{'2i', [{<<"us">>, {LUser, LServer}}]}]).
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_data(LUser, LServer, Data).
get_data(LUser, LServer, Data) ->
get_data(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE), Data, []).
Mod = gen_mod:db_mod(LServer, ?MODULE),
get_data(LUser, LServer, Data, Mod, []).
get_data(_LUser, _LServer, _DBType, [],
Storage_Xmlels) ->
get_data(_LUser, _LServer, [], _Mod, Storage_Xmlels) ->
lists:reverse(Storage_Xmlels);
get_data(LUser, LServer, mnesia,
[{XmlNS, Xmlel} | Data], Storage_Xmlels) ->
case mnesia:dirty_read(private_storage,
{LUser, LServer, XmlNS})
of
[#private_storage{xml = Storage_Xmlel}] ->
get_data(LUser, LServer, mnesia, Data,
[Storage_Xmlel | Storage_Xmlels]);
_ ->
get_data(LUser, LServer, mnesia, Data,
[Xmlel | Storage_Xmlels])
end;
get_data(LUser, LServer, odbc, [{XMLNS, El} | Els],
Res) ->
case catch odbc_queries:get_private_data(LServer,
LUser, XMLNS)
of
{selected, [{SData}]} ->
case fxml_stream:parse_element(SData) of
Data when is_record(Data, xmlel) ->
get_data(LUser, LServer, odbc, Els, [Data | Res])
end;
_ -> get_data(LUser, LServer, odbc, Els, [El | Res])
end;
get_data(LUser, LServer, riak, [{XMLNS, El} | Els],
Res) ->
case ejabberd_riak:get(private_storage, private_storage_schema(),
{LUser, LServer, XMLNS}) of
{ok, #private_storage{xml = NewEl}} ->
get_data(LUser, LServer, riak, Els, [NewEl|Res]);
_ ->
get_data(LUser, LServer, riak, Els, [El|Res])
get_data(LUser, LServer, [{XmlNS, Xmlel} | Data], Mod, Storage_Xmlels) ->
case Mod:get_data(LUser, LServer, XmlNS) of
{ok, Storage_Xmlel} ->
get_data(LUser, LServer, Data, Mod, [Storage_Xmlel | Storage_Xmlels]);
error ->
get_data(LUser, LServer, Data, Mod, [Xmlel | Storage_Xmlels])
end.
get_data(LUser, LServer) ->
get_all_data(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
get_all_data(LUser, LServer, mnesia) ->
lists:flatten(
mnesia:dirty_select(private_storage,
[{#private_storage{usns = {LUser, LServer, '_'},
xml = '$1'},
[], ['$1']}]));
get_all_data(LUser, LServer, odbc) ->
case catch odbc_queries:get_private_data(LServer, LUser) of
{selected, Res} ->
lists:flatmap(
fun({_, SData}) ->
case fxml_stream:parse_element(SData) of
#xmlel{} = El ->
[El];
_ ->
[]
end
end, Res);
_ ->
[]
end;
get_all_data(LUser, LServer, riak) ->
case ejabberd_riak:get_by_index(
private_storage, private_storage_schema(),
<<"us">>, {LUser, LServer}) of
{ok, Res} ->
[El || #private_storage{xml = El} <- Res];
_ ->
[]
end.
private_storage_schema() ->
{record_info(fields, private_storage), #private_storage{}}.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_all_data(LUser, LServer).
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
remove_user(LUser, LServer,
gen_mod:db_type(Server, ?MODULE)).
Mod = gen_mod:db_mod(Server, ?MODULE),
Mod:remove_user(LUser, LServer).
remove_user(LUser, LServer, mnesia) ->
F = fun () ->
Namespaces = mnesia:select(private_storage,
[{#private_storage{usns =
{LUser,
LServer,
'$1'},
_ = '_'},
[], ['$$']}]),
lists:foreach(fun ([Namespace]) ->
mnesia:delete({private_storage,
{LUser, LServer,
Namespace}})
end,
Namespaces)
end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
odbc_queries:del_user_private_storage(LServer, LUser);
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete_by_index(private_storage,
<<"us">>, {LUser, LServer})}.
update_table() ->
Fields = record_info(fields, private_storage),
case mnesia:table_info(private_storage, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
private_storage, Fields, set,
fun(#private_storage{usns = {U, _, _}}) -> U end,
fun(#private_storage{usns = {U, S, NS}, xml = El} = R) ->
R#private_storage{usns = {iolist_to_binary(U),
iolist_to_binary(S),
iolist_to_binary(NS)},
xml = fxml:to_xmlel(El)}
end);
_ ->
?INFO_MSG("Recreating private_storage table", []),
mnesia:transform_table(private_storage, ignore, Fields)
end.
export(_Server) ->
[{private_storage,
fun(Host, #private_storage{usns = {LUser, LServer, XMLNS},
xml = Data})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
LXMLNS = ejabberd_odbc:escape(XMLNS),
SData =
ejabberd_odbc:escape(fxml:element_to_binary(Data)),
odbc_queries:set_private_data_sql(Username, LXMLNS,
SData);
(_Host, _R) ->
[]
end}].
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
import(LServer) ->
[{<<"select username, namespace, data from private_storage;">>,
fun([LUser, XMLNS, XML]) ->
El = #xmlel{} = fxml_stream:parse_element(XML),
#private_storage{usns = {LUser, LServer, XMLNS},
xml = El}
end}].
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:import(LServer).
import(_LServer, mnesia, #private_storage{} = PS) ->
mnesia:dirty_write(PS);
import(_LServer, riak, #private_storage{usns = {LUser, LServer, _}} = PS) ->
ejabberd_riak:put(PS, private_storage_schema(),
[{'2i', [{<<"us">>, {LUser, LServer}}]}]);
import(_, _, _) ->
pass.
import(LServer, DBType, PD) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, PD).
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;

View File

@ -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.

67
src/mod_private_riak.erl Normal file
View File

@ -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{}}.

97
src/mod_private_sql.erl Normal file
View File

@ -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
%%%===================================================================

View File

@ -49,7 +49,6 @@
get_jid_info/4, item_to_xml/1, webadmin_page/3,
webadmin_user/4, get_versioning_feature/2,
roster_versioning_enabled/1, roster_version/2,
record_to_string/1, groups_to_string/1,
mod_opt_type/1, set_roster/1]).
-include("ejabberd.hrl").
@ -65,23 +64,27 @@
-export_type([subscription/0]).
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #roster{} | #roster_version{}) -> ok | pass.
-callback read_roster_version(binary(), binary()) -> binary() | error.
-callback write_roster_version(binary(), binary(), boolean(), binary()) -> any().
-callback get_roster(binary(), binary()) -> [#roster{}].
-callback get_roster_by_jid(binary(), binary(), ljid()) -> #roster{}.
-callback get_only_items(binary(), binary()) -> [#roster{}].
-callback roster_subscribe(binary(), binary(), ljid(), #roster{}) -> any().
-callback transaction(binary(), function()) -> {atomic, any()} | {aborted, any()}.
-callback get_roster_by_jid_with_groups(binary(), binary(), ljid()) -> #roster{}.
-callback remove_user(binary(), binary()) -> {atomic, any()}.
-callback update_roster(binary(), binary(), ljid(), #roster{}) -> any().
-callback del_roster(binary(), binary(), ljid()) -> any().
-callback read_subscription_and_groups(binary(), binary(), ljid()) ->
{subscription(), [binary()]}.
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
one_queue),
case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(roster,
[{disc_copies, [node()]},
{attributes, record_info(fields, roster)}]),
mnesia:create_table(roster_version,
[{disc_copies, [node()]},
{attributes,
record_info(fields, roster_version)}]),
update_tables(),
mnesia:add_table_index(roster, us),
mnesia:add_table_index(roster_version, us);
_ -> ok
end,
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
ejabberd_hooks:add(roster_get, Host, ?MODULE,
get_user_roster, 50),
ejabberd_hooks:add(roster_in_subscription, Host,
@ -194,26 +197,8 @@ roster_version(LServer, LUser) ->
end.
read_roster_version(LUser, LServer) ->
read_roster_version(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
read_roster_version(LUser, LServer, mnesia) ->
US = {LUser, LServer},
case mnesia:dirty_read(roster_version, US) of
[#roster_version{version = V}] -> V;
[] -> error
end;
read_roster_version(LUser, LServer, odbc) ->
case odbc_queries:get_roster_version(LServer, LUser) of
{selected, [{Version}]} -> Version;
{selected, []} -> error
end;
read_roster_version(LServer, LUser, riak) ->
case ejabberd_riak:get(roster_version, roster_version_schema(),
{LUser, LServer}) of
{ok, #roster_version{version = V}} -> V;
_Err -> error
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:read_roster_version(LUser, LServer).
write_roster_version(LUser, LServer) ->
write_roster_version(LUser, LServer, false).
@ -223,38 +208,10 @@ write_roster_version_t(LUser, LServer) ->
write_roster_version(LUser, LServer, InTransaction) ->
Ver = p1_sha:sha(term_to_binary(p1_time_compat:unique_integer())),
write_roster_version(LUser, LServer, InTransaction, Ver,
gen_mod:db_type(LServer, ?MODULE)),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:write_roster_version(LUser, LServer, InTransaction, Ver),
Ver.
write_roster_version(LUser, LServer, InTransaction, Ver,
mnesia) ->
US = {LUser, LServer},
if InTransaction ->
mnesia:write(#roster_version{us = US, version = Ver});
true ->
mnesia:dirty_write(#roster_version{us = US,
version = Ver})
end;
write_roster_version(LUser, LServer, InTransaction, Ver,
odbc) ->
Username = ejabberd_odbc:escape(LUser),
EVer = ejabberd_odbc:escape(Ver),
if InTransaction ->
odbc_queries:set_roster_version(Username, EVer);
true ->
odbc_queries:sql_transaction(LServer,
fun () ->
odbc_queries:set_roster_version(Username,
EVer)
end)
end;
write_roster_version(LUser, LServer, _InTransaction, Ver,
riak) ->
US = {LUser, LServer},
ejabberd_riak:put(#roster_version{us = US, version = Ver},
roster_version_schema()).
%% Load roster from DB only if neccesary.
%% It is neccesary if
%% - roster versioning is disabled in server OR
@ -350,56 +307,8 @@ get_user_roster(Acc, {LUser, LServer}) ->
++ Acc.
get_roster(LUser, LServer) ->
get_roster(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
get_roster(LUser, LServer, mnesia) ->
US = {LUser, LServer},
case catch mnesia:dirty_index_read(roster, US,
#roster.us)
of
Items when is_list(Items)-> Items;
_ -> []
end;
get_roster(LUser, LServer, riak) ->
case ejabberd_riak:get_by_index(roster, roster_schema(),
<<"us">>, {LUser, LServer}) of
{ok, Items} -> Items;
_Err -> []
end;
get_roster(LUser, LServer, odbc) ->
case catch odbc_queries:get_roster(LServer, LUser) of
{selected, Items} when is_list(Items) ->
JIDGroups = case catch odbc_queries:get_roster_jid_groups(
LServer, LUser) of
{selected, JGrps}
when is_list(JGrps) ->
JGrps;
_ -> []
end,
GroupsDict = lists:foldl(fun({J, G}, Acc) ->
dict:append(J, G, Acc)
end,
dict:new(), JIDGroups),
RItems =
lists:flatmap(
fun(I) ->
case raw_to_record(LServer, I) of
%% Bad JID in database:
error -> [];
R ->
SJID = jid:to_string(R#roster.jid),
Groups = case dict:find(SJID, GroupsDict) of
{ok, Gs} -> Gs;
error -> []
end,
[R#roster{groups = Groups}]
end
end,
Items),
RItems;
_ -> []
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_roster(LUser, LServer).
set_roster(#roster{us = {LUser, LServer}, jid = LJID} = Item) ->
transaction(
@ -437,48 +346,8 @@ item_to_xml(Item) ->
children = SubEls}.
get_roster_by_jid_t(LUser, LServer, LJID) ->
DBType = gen_mod:db_type(LServer, ?MODULE),
get_roster_by_jid_t(LUser, LServer, LJID, DBType).
get_roster_by_jid_t(LUser, LServer, LJID, mnesia) ->
case mnesia:read({roster, {LUser, LServer, LJID}}) of
[] ->
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = LJID};
[I] ->
I#roster{jid = LJID, name = <<"">>, groups = [],
xs = []}
end;
get_roster_by_jid_t(LUser, LServer, LJID, odbc) ->
{selected, Res} =
odbc_queries:get_roster_by_jid(LServer, LUser, jid:to_string(LJID)),
case Res of
[] ->
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = LJID};
[I] ->
R = raw_to_record(LServer, I),
case R of
%% Bad JID in database:
error ->
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = LJID};
_ ->
R#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = LJID, name = <<"">>}
end
end;
get_roster_by_jid_t(LUser, LServer, LJID, riak) ->
case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
{ok, I} ->
I#roster{jid = LJID, name = <<"">>, groups = [],
xs = []};
{error, notfound} ->
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = LJID};
Err ->
exit(Err)
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_roster_by_jid(LUser, LServer, LJID).
try_process_iq_set(From, To, #iq{sub_el = SubEl, lang = Lang} = IQ) ->
#jid{server = Server} = From,
@ -632,77 +501,37 @@ push_item_version(Server, User, From, Item,
end,
ejabberd_sm:get_user_resources(User, Server)).
get_subscription_lists(Acc, User, Server) ->
get_subscription_lists(_Acc, User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
DBType = gen_mod:db_type(LServer, ?MODULE),
Items = get_subscription_lists(Acc, LUser, LServer,
DBType),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Items = Mod:get_only_items(LUser, LServer),
fill_subscription_lists(LServer, Items, [], []).
get_subscription_lists(_, LUser, LServer, mnesia) ->
US = {LUser, LServer},
case mnesia:dirty_index_read(roster, US, #roster.us) of
Items when is_list(Items) -> Items;
_ -> []
end;
get_subscription_lists(_, LUser, LServer, odbc) ->
case catch odbc_queries:get_roster(LServer, LUser) of
{selected, Items} when is_list(Items) ->
lists:map(fun(I) -> raw_to_record(LServer, I) end, Items);
_ -> []
end;
get_subscription_lists(_, LUser, LServer, riak) ->
case ejabberd_riak:get_by_index(roster, roster_schema(),
<<"us">>, {LUser, LServer}) of
{ok, Items} -> Items;
_Err -> []
end.
fill_subscription_lists(LServer, [#roster{} = I | Is],
F, T) ->
fill_subscription_lists(LServer, [I | Is], F, T) ->
J = element(3, I#roster.usj),
case I#roster.subscription of
both ->
fill_subscription_lists(LServer, Is, [J | F], [J | T]);
from ->
fill_subscription_lists(LServer, Is, [J | F], T);
to -> fill_subscription_lists(LServer, Is, F, [J | T]);
_ -> fill_subscription_lists(LServer, Is, F, T)
both ->
fill_subscription_lists(LServer, Is, [J | F], [J | T]);
from ->
fill_subscription_lists(LServer, Is, [J | F], T);
to -> fill_subscription_lists(LServer, Is, F, [J | T]);
_ -> fill_subscription_lists(LServer, Is, F, T)
end;
fill_subscription_lists(LServer, [RawI | Is], F, T) ->
I = raw_to_record(LServer, RawI),
case I of
%% Bad JID in database:
error -> fill_subscription_lists(LServer, Is, F, T);
_ -> fill_subscription_lists(LServer, [I | Is], F, T)
end;
fill_subscription_lists(_LServer, [], F, T) -> {F, T}.
fill_subscription_lists(_LServer, [], F, T) ->
{F, T}.
ask_to_pending(subscribe) -> out;
ask_to_pending(unsubscribe) -> none;
ask_to_pending(Ask) -> Ask.
roster_subscribe_t(LUser, LServer, LJID, Item) ->
DBType = gen_mod:db_type(LServer, ?MODULE),
roster_subscribe_t(LUser, LServer, LJID, Item, DBType).
roster_subscribe_t(_LUser, _LServer, _LJID, Item,
mnesia) ->
mnesia:write(Item);
roster_subscribe_t(_LUser, _LServer, _LJID, Item, odbc) ->
ItemVals = record_to_row(Item),
odbc_queries:roster_subscribe(ItemVals);
roster_subscribe_t(LUser, LServer, _LJID, Item, riak) ->
ejabberd_riak:put(Item, roster_schema(),
[{'2i', [{<<"us">>, {LUser, LServer}}]}]).
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:roster_subscribe(LUser, LServer, LJID, Item).
transaction(LServer, F) ->
case gen_mod:db_type(LServer, ?MODULE) of
mnesia -> mnesia:transaction(F);
odbc -> ejabberd_odbc:sql_transaction(LServer, F);
riak -> {atomic, F()}
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:transaction(LServer, F).
in_subscription(_, User, Server, JID, Type, Reason) ->
process_subscription(in, User, Server, JID, Type,
@ -712,45 +541,8 @@ out_subscription(User, Server, JID, Type) ->
process_subscription(out, User, Server, JID, Type, <<"">>).
get_roster_by_jid_with_groups_t(LUser, LServer, LJID) ->
DBType = gen_mod:db_type(LServer, ?MODULE),
get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
DBType).
get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
mnesia) ->
case mnesia:read({roster, {LUser, LServer, LJID}}) of
[] ->
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = LJID};
[I] -> I
end;
get_roster_by_jid_with_groups_t(LUser, LServer, LJID,
odbc) ->
SJID = jid:to_string(LJID),
case odbc_queries:get_roster_by_jid(LServer, LUser, SJID) of
{selected, [I]} ->
R = raw_to_record(LServer, I),
Groups =
case odbc_queries:get_roster_groups(LServer, LUser, SJID) of
{selected, JGrps} when is_list(JGrps) ->
[JGrp || {JGrp} <- JGrps];
_ -> []
end,
R#roster{groups = Groups};
{selected, []} ->
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = LJID}
end;
get_roster_by_jid_with_groups_t(LUser, LServer, LJID, riak) ->
case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
{ok, I} ->
I;
{error, notfound} ->
#roster{usj = {LUser, LServer, LJID},
us = {LUser, LServer}, jid = LJID};
Err ->
exit(Err)
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_roster_by_jid_with_groups(LUser, LServer, LJID).
process_subscription(Direction, User, Server, JID1,
Type, Reason) ->
@ -948,21 +740,8 @@ remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
send_unsubscription_to_rosteritems(LUser, LServer),
remove_user(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
remove_user(LUser, LServer, mnesia) ->
US = {LUser, LServer},
F = fun () ->
lists:foreach(fun (R) -> mnesia:delete_object(R) end,
mnesia:index_read(roster, US, #roster.us))
end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
odbc_queries:del_user_roster_t(LServer, LUser),
ok;
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete_by_index(roster, <<"us">>, {LUser, LServer})}.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_user(LUser, LServer).
%% For each contact with Subscription:
%% Both or From, send a "unsubscribed" presence stanza;
@ -1020,33 +799,12 @@ set_items(User, Server, SubEl) ->
transaction(LServer, F).
update_roster_t(LUser, LServer, LJID, Item) ->
DBType = gen_mod:db_type(LServer, ?MODULE),
update_roster_t(LUser, LServer, LJID, Item, DBType).
update_roster_t(_LUser, _LServer, _LJID, Item,
mnesia) ->
mnesia:write(Item);
update_roster_t(LUser, LServer, LJID, Item, odbc) ->
SJID = jid:to_string(LJID),
ItemVals = record_to_row(Item),
ItemGroups = Item#roster.groups,
odbc_queries:update_roster(LServer, LUser, SJID, ItemVals,
ItemGroups);
update_roster_t(LUser, LServer, _LJID, Item, riak) ->
ejabberd_riak:put(Item, roster_schema(),
[{'2i', [{<<"us">>, {LUser, LServer}}]}]).
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:update_roster(LUser, LServer, LJID, Item).
del_roster_t(LUser, LServer, LJID) ->
DBType = gen_mod:db_type(LServer, ?MODULE),
del_roster_t(LUser, LServer, LJID, DBType).
del_roster_t(LUser, LServer, LJID, mnesia) ->
mnesia:delete({roster, {LUser, LServer, LJID}});
del_roster_t(LUser, LServer, LJID, odbc) ->
SJID = jid:to_string(LJID),
odbc_queries:del_roster(LServer, LUser, SJID);
del_roster_t(LUser, LServer, LJID, riak) ->
ejabberd_riak:delete(roster, {LUser, LServer, LJID}).
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:del_roster(LUser, LServer, LJID).
process_item_set_t(LUser, LServer,
#xmlel{attrs = Attrs, children = Els}) ->
@ -1109,13 +867,12 @@ process_item_attrs_ws(Item, []) -> Item.
get_in_pending_subscriptions(Ls, User, Server) ->
LServer = jid:nameprep(Server),
get_in_pending_subscriptions(Ls, User, Server,
gen_mod:db_type(LServer, ?MODULE)).
Mod = gen_mod:db_mod(LServer, ?MODULE),
get_in_pending_subscriptions(Ls, User, Server, Mod).
get_in_pending_subscriptions(Ls, User, Server, DBType)
when DBType == mnesia; DBType == riak ->
get_in_pending_subscriptions(Ls, User, Server, Mod) ->
JID = jid:make(User, Server, <<"">>),
Result = get_roster(JID#jid.luser, JID#jid.lserver, DBType),
Result = Mod:get_only_items(JID#jid.luser, JID#jid.lserver),
Ls ++ lists:map(fun (R) ->
Message = R#roster.askmessage,
Status = if is_binary(Message) -> (Message);
@ -1140,93 +897,15 @@ get_in_pending_subscriptions(Ls, User, Server, DBType)
_ -> false
end
end,
Result));
get_in_pending_subscriptions(Ls, User, Server, odbc) ->
JID = jid:make(User, Server, <<"">>),
LUser = JID#jid.luser,
LServer = JID#jid.lserver,
case catch odbc_queries:get_roster(LServer, LUser) of
{selected, Items} when is_list(Items) ->
Ls ++
lists:map(fun (R) ->
Message = R#roster.askmessage,
#xmlel{name = <<"presence">>,
attrs =
[{<<"from">>,
jid:to_string(R#roster.jid)},
{<<"to">>, jid:to_string(JID)},
{<<"type">>, <<"subscribe">>}],
children =
[#xmlel{name = <<"status">>,
attrs = [],
children =
[{xmlcdata, Message}]}]}
end,
lists:flatmap(fun (I) ->
case raw_to_record(LServer, I) of
%% Bad JID in database:
error -> [];
R ->
case R#roster.ask of
in -> [R];
both -> [R];
_ -> []
end
end
end,
Items));
_ -> Ls
end.
Result)).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
read_subscription_and_groups(User, Server, LJID) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
read_subscription_and_groups(LUser, LServer, LJID,
gen_mod:db_type(LServer, ?MODULE)).
read_subscription_and_groups(LUser, LServer, LJID,
mnesia) ->
case catch mnesia:dirty_read(roster,
{LUser, LServer, LJID})
of
[#roster{subscription = Subscription,
groups = Groups}] ->
{Subscription, Groups};
_ -> error
end;
read_subscription_and_groups(LUser, LServer, LJID,
odbc) ->
SJID = jid:to_string(LJID),
case catch odbc_queries:get_subscription(LServer, LUser, SJID) of
{selected, [{SSubscription}]} ->
Subscription = case SSubscription of
<<"B">> -> both;
<<"T">> -> to;
<<"F">> -> from;
_ -> none
end,
Groups = case catch
odbc_queries:get_rostergroup_by_jid(LServer, LUser,
SJID)
of
{selected, JGrps} when is_list(JGrps) ->
[JGrp || {JGrp} <- JGrps];
_ -> []
end,
{Subscription, Groups};
_ -> error
end;
read_subscription_and_groups(LUser, LServer, LJID,
riak) ->
case ejabberd_riak:get(roster, roster_schema(), {LUser, LServer, LJID}) of
{ok, #roster{subscription = Subscription,
groups = Groups}} ->
{Subscription, Groups};
_ ->
error
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:read_subscription_and_groups(LUser, LServer, LJID).
get_jid_info(_, User, Server, JID) ->
LJID = jid:tolower(JID),
@ -1246,155 +925,6 @@ get_jid_info(_, User, Server, JID) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
raw_to_record(LServer,
[User, SJID, Nick, SSubscription, SAsk, SAskMessage,
_SServer, _SSubscribe, _SType]) ->
raw_to_record(LServer,
{User, SJID, Nick, SSubscription, SAsk, SAskMessage,
_SServer, _SSubscribe, _SType});
raw_to_record(LServer,
{User, SJID, Nick, SSubscription, SAsk, SAskMessage,
_SServer, _SSubscribe, _SType}) ->
case jid:from_string(SJID) of
error -> error;
JID ->
LJID = jid:tolower(JID),
Subscription = case SSubscription of
<<"B">> -> both;
<<"T">> -> to;
<<"F">> -> from;
_ -> none
end,
Ask = case SAsk of
<<"S">> -> subscribe;
<<"U">> -> unsubscribe;
<<"B">> -> both;
<<"O">> -> out;
<<"I">> -> in;
_ -> none
end,
#roster{usj = {User, LServer, LJID},
us = {User, LServer}, jid = LJID, name = Nick,
subscription = Subscription, ask = Ask,
askmessage = SAskMessage}
end.
record_to_string(#roster{us = {User, _Server},
jid = JID, name = Name, subscription = Subscription,
ask = Ask, askmessage = AskMessage}) ->
Username = ejabberd_odbc:escape(User),
SJID =
ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))),
Nick = ejabberd_odbc:escape(Name),
SSubscription = case Subscription of
both -> <<"B">>;
to -> <<"T">>;
from -> <<"F">>;
none -> <<"N">>
end,
SAsk = case Ask of
subscribe -> <<"S">>;
unsubscribe -> <<"U">>;
both -> <<"B">>;
out -> <<"O">>;
in -> <<"I">>;
none -> <<"N">>
end,
SAskMessage = ejabberd_odbc:escape(AskMessage),
[Username, SJID, Nick, SSubscription, SAsk, SAskMessage,
<<"N">>, <<"">>, <<"item">>].
record_to_row(
#roster{us = {LUser, _LServer},
jid = JID, name = Name, subscription = Subscription,
ask = Ask, askmessage = AskMessage}) ->
SJID = jid:to_string(jid:tolower(JID)),
SSubscription = case Subscription of
both -> <<"B">>;
to -> <<"T">>;
from -> <<"F">>;
none -> <<"N">>
end,
SAsk = case Ask of
subscribe -> <<"S">>;
unsubscribe -> <<"U">>;
both -> <<"B">>;
out -> <<"O">>;
in -> <<"I">>;
none -> <<"N">>
end,
{LUser, SJID, Name, SSubscription, SAsk, AskMessage}.
groups_to_string(#roster{us = {User, _Server},
jid = JID, groups = Groups}) ->
Username = ejabberd_odbc:escape(User),
SJID =
ejabberd_odbc:escape(jid:to_string(jid:tolower(JID))),
lists:foldl(fun (<<"">>, Acc) -> Acc;
(Group, Acc) ->
G = ejabberd_odbc:escape(Group),
[[Username, SJID, G] | Acc]
end,
[], Groups).
update_tables() ->
update_roster_table(),
update_roster_version_table().
update_roster_table() ->
Fields = record_info(fields, roster),
case mnesia:table_info(roster, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
roster, Fields, set,
fun(#roster{usj = {U, _, _}}) -> U end,
fun(#roster{usj = {U, S, {LU, LS, LR}},
us = {U1, S1},
jid = {U2, S2, R2},
name = Name,
groups = Gs,
askmessage = Ask,
xs = Xs} = R) ->
R#roster{usj = {iolist_to_binary(U),
iolist_to_binary(S),
{iolist_to_binary(LU),
iolist_to_binary(LS),
iolist_to_binary(LR)}},
us = {iolist_to_binary(U1),
iolist_to_binary(S1)},
jid = {iolist_to_binary(U2),
iolist_to_binary(S2),
iolist_to_binary(R2)},
name = iolist_to_binary(Name),
groups = [iolist_to_binary(G) || G <- Gs],
askmessage = try iolist_to_binary(Ask)
catch _:_ -> <<"">> end,
xs = [fxml:to_xmlel(X) || X <- Xs]}
end);
_ ->
?INFO_MSG("Recreating roster table", []),
mnesia:transform_table(roster, ignore, Fields)
end.
%% Convert roster table to support virtual host
%% Convert roster table: xattrs fields become
update_roster_version_table() ->
Fields = record_info(fields, roster_version),
case mnesia:table_info(roster_version, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
roster_version, Fields, set,
fun(#roster_version{us = {U, _}}) -> U end,
fun(#roster_version{us = {U, S}, version = Ver} = R) ->
R#roster_version{us = {iolist_to_binary(U),
iolist_to_binary(S)},
version = iolist_to_binary(Ver)}
end);
_ ->
?INFO_MSG("Recreating roster_version table", []),
mnesia:transform_table(roster_version, ignore, Fields)
end.
webadmin_page(_, Host,
#request{us = _US, path = [<<"user">>, U, <<"roster">>],
q = Query, lang = Lang} =
@ -1692,68 +1222,17 @@ is_managed_from_id(<<"roster-remotely-managed">>) ->
is_managed_from_id(_Id) ->
false.
roster_schema() ->
{record_info(fields, roster), #roster{}}.
roster_version_schema() ->
{record_info(fields, roster_version), #roster_version{}}.
export(_Server) ->
[{roster,
fun(Host, #roster{usj = {LUser, LServer, LJID}} = R)
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
SJID = ejabberd_odbc:escape(jid:to_string(LJID)),
ItemVals = record_to_string(R),
ItemGroups = groups_to_string(R),
odbc_queries:update_roster_sql(Username, SJID,
ItemVals, ItemGroups);
(_Host, _R) ->
[]
end},
{roster_version,
fun(Host, #roster_version{us = {LUser, LServer}, version = Ver})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
SVer = ejabberd_odbc:escape(Ver),
[[<<"delete from roster_version where username='">>,
Username, <<"';">>],
[<<"insert into roster_version(username, version) values('">>,
Username, <<"', '">>, SVer, <<"');">>]];
(_Host, _R) ->
[]
end}].
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
import(LServer) ->
[{<<"select username, jid, nick, subscription, "
"ask, askmessage, server, subscribe, type from rosterusers;">>,
fun([LUser, JID|_] = Row) ->
Item = raw_to_record(LServer, Row),
Username = ejabberd_odbc:escape(LUser),
SJID = ejabberd_odbc:escape(JID),
{selected, _, Rows} =
ejabberd_odbc:sql_query_t(
[<<"select grp from rostergroups where username='">>,
Username, <<"' and jid='">>, SJID, <<"'">>]),
Groups = [Grp || [Grp] <- Rows],
Item#roster{groups = Groups}
end},
{<<"select username, version from roster_version;">>,
fun([LUser, Ver]) ->
#roster_version{us = {LUser, LServer}, version = Ver}
end}].
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:import(LServer).
import(_LServer, mnesia, #roster{} = R) ->
mnesia:dirty_write(R);
import(_LServer, mnesia, #roster_version{} = RV) ->
mnesia:dirty_write(RV);
import(_LServer, riak, #roster{us = {LUser, LServer}} = R) ->
ejabberd_riak:put(R, roster_schema(),
[{'2i', [{<<"us">>, {LUser, LServer}}]}]);
import(_LServer, riak, #roster_version{} = RV) ->
ejabberd_riak:put(RV, roster_version_schema());
import(_, _, _) ->
pass.
import(LServer, DBType, R) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, R).
mod_opt_type(access) ->
fun (A) when is_atom(A) -> A end;

171
src/mod_roster_mnesia.erl Normal file
View File

@ -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.

113
src/mod_roster_riak.erl Normal file
View File

@ -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{}}.

308
src/mod_roster_sql.erl Normal file
View File

@ -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).

View File

@ -38,7 +38,7 @@
list_groups/1, create_group/2, create_group/3,
delete_group/2, get_group_opts/2, set_group_opts/3,
get_group_users/2, get_group_explicit_users/2,
is_user_in_group/3, add_user_to_group/3,
is_user_in_group/3, add_user_to_group/3, opts_to_binary/1,
remove_user_from_group/3, mod_opt_type/1]).
-include("ejabberd.hrl").
@ -52,25 +52,28 @@
-include("ejabberd_web_admin.hrl").
-record(sr_group, {group_host = {<<"">>, <<"">>} :: {'$1' | binary(), '$2' | binary()},
opts = [] :: list() | '_' | '$2'}).
-include("mod_shared_roster.hrl").
-record(sr_user, {us = {<<"">>, <<"">>} :: {binary(), binary()},
group_host = {<<"">>, <<"">>} :: {binary(), binary()}}).
-type group_options() :: [{atom(), any()}].
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #sr_user{} | #sr_group{}) -> ok | pass.
-callback list_groups(binary()) -> [binary()].
-callback groups_with_opts(binary()) -> [{binary(), group_options()}].
-callback create_group(binary(), binary(), group_options()) -> {atomic, any()}.
-callback delete_group(binary(), binary()) -> {atomic, any()}.
-callback get_group_opts(binary(), binary()) -> group_options() | error.
-callback set_group_opts(binary(), binary(), group_options()) -> {atomic, any()}.
-callback get_user_groups({binary(), binary()}, binary()) -> [binary()].
-callback get_group_explicit_users(binary(), binary()) -> [{binary(), binary()}].
-callback get_user_displayed_groups(binary(), binary(), group_options()) ->
[{binary(), group_options()}].
-callback is_user_in_group({binary(), binary()}, binary(), binary()) -> boolean().
-callback add_user_to_group(binary(), {binary(), binary()}, binary()) -> {atomic, any()}.
-callback remove_user_from_group(binary(), {binary(), binary()}, binary()) -> {atomic, any()}.
start(Host, Opts) ->
case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(sr_group,
[{disc_copies, [node()]},
{attributes, record_info(fields, sr_group)}]),
mnesia:create_table(sr_user,
[{disc_copies, [node()]}, {type, bag},
{attributes, record_info(fields, sr_user)}]),
update_tables(),
mnesia:add_table_index(sr_user, group_host);
_ -> ok
end,
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
ejabberd_hooks:add(webadmin_menu_host, Host, ?MODULE,
webadmin_menu, 70),
ejabberd_hooks:add(webadmin_page_host, Host, ?MODULE,
@ -391,195 +394,36 @@ process_subscription(Direction, User, Server, JID,
end.
list_groups(Host) ->
list_groups(Host, gen_mod:db_type(Host, ?MODULE)).
list_groups(Host, mnesia) ->
mnesia:dirty_select(sr_group,
[{#sr_group{group_host = {'$1', '$2'}, _ = '_'},
[{'==', '$2', Host}], ['$1']}]);
list_groups(Host, riak) ->
case ejabberd_riak:get_keys_by_index(sr_group, <<"host">>, Host) of
{ok, Gs} ->
[G || {G, _} <- Gs];
_ ->
[]
end;
list_groups(Host, odbc) ->
case ejabberd_odbc:sql_query(Host,
[<<"select name from sr_group;">>])
of
{selected, [<<"name">>], Rs} -> [G || [G] <- Rs];
_ -> []
end.
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:list_groups(Host).
groups_with_opts(Host) ->
groups_with_opts(Host, gen_mod:db_type(Host, ?MODULE)).
groups_with_opts(Host, mnesia) ->
Gs = mnesia:dirty_select(sr_group,
[{#sr_group{group_host = {'$1', Host}, opts = '$2',
_ = '_'},
[], [['$1', '$2']]}]),
lists:map(fun ([G, O]) -> {G, O} end, Gs);
groups_with_opts(Host, riak) ->
case ejabberd_riak:get_by_index(sr_group, sr_group_schema(),
<<"host">>, Host) of
{ok, Rs} ->
[{G, O} || #sr_group{group_host = {G, _}, opts = O} <- Rs];
_ ->
[]
end;
groups_with_opts(Host, odbc) ->
case ejabberd_odbc:sql_query(Host,
[<<"select name, opts from sr_group;">>])
of
{selected, [<<"name">>, <<"opts">>], Rs} ->
[{G, opts_to_binary(ejabberd_odbc:decode_term(Opts))}
|| [G, Opts] <- Rs];
_ -> []
end.
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:groups_with_opts(Host).
create_group(Host, Group) ->
create_group(Host, Group, []).
create_group(Host, Group, Opts) ->
create_group(Host, Group, Opts,
gen_mod:db_type(Host, ?MODULE)).
create_group(Host, Group, Opts, mnesia) ->
R = #sr_group{group_host = {Group, Host}, opts = Opts},
F = fun () -> mnesia:write(R) end,
mnesia:transaction(F);
create_group(Host, Group, Opts, riak) ->
{atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
opts = Opts},
sr_group_schema(),
[{'2i', [{<<"host">>, Host}]}])};
create_group(Host, Group, Opts, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
SOpts = ejabberd_odbc:encode_term(Opts),
F = fun () ->
odbc_queries:update_t(<<"sr_group">>,
[<<"name">>, <<"opts">>], [SGroup, SOpts],
[<<"name='">>, SGroup, <<"'">>])
end,
ejabberd_odbc:sql_transaction(Host, F).
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:create_group(Host, Group, Opts).
delete_group(Host, Group) ->
delete_group(Host, Group,
gen_mod:db_type(Host, ?MODULE)).
delete_group(Host, Group, mnesia) ->
GroupHost = {Group, Host},
F = fun () ->
mnesia:delete({sr_group, GroupHost}),
Users = mnesia:index_read(sr_user, GroupHost,
#sr_user.group_host),
lists:foreach(fun (UserEntry) ->
mnesia:delete_object(UserEntry)
end,
Users)
end,
mnesia:transaction(F);
delete_group(Host, Group, riak) ->
try
ok = ejabberd_riak:delete(sr_group, {Group, Host}),
ok = ejabberd_riak:delete_by_index(sr_user, <<"group_host">>,
{Group, Host}),
{atomic, ok}
catch _:{badmatch, Err} ->
{atomic, Err}
end;
delete_group(Host, Group, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
F = fun () ->
ejabberd_odbc:sql_query_t([<<"delete from sr_group where name='">>,
SGroup, <<"';">>]),
ejabberd_odbc:sql_query_t([<<"delete from sr_user where grp='">>,
SGroup, <<"';">>])
end,
case ejabberd_odbc:sql_transaction(Host, F) of
{atomic,{updated,_}} -> {atomic, ok};
Res -> Res
end.
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:delete_group(Host, Group).
get_group_opts(Host, Group) ->
get_group_opts(Host, Group,
gen_mod:db_type(Host, ?MODULE)).
get_group_opts(Host, Group, mnesia) ->
case catch mnesia:dirty_read(sr_group, {Group, Host}) of
[#sr_group{opts = Opts}] -> Opts;
_ -> error
end;
get_group_opts(Host, Group, riak) ->
case ejabberd_riak:get(sr_group, sr_group_schema(), {Group, Host}) of
{ok, #sr_group{opts = Opts}} -> Opts;
_ -> error
end;
get_group_opts(Host, Group, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
case catch ejabberd_odbc:sql_query(Host,
[<<"select opts from sr_group where name='">>,
SGroup, <<"';">>])
of
{selected, [<<"opts">>], [[SOpts]]} ->
opts_to_binary(ejabberd_odbc:decode_term(SOpts));
_ -> error
end.
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:get_group_opts(Host, Group).
set_group_opts(Host, Group, Opts) ->
set_group_opts(Host, Group, Opts,
gen_mod:db_type(Host, ?MODULE)).
set_group_opts(Host, Group, Opts, mnesia) ->
R = #sr_group{group_host = {Group, Host}, opts = Opts},
F = fun () -> mnesia:write(R) end,
mnesia:transaction(F);
set_group_opts(Host, Group, Opts, riak) ->
{atomic, ejabberd_riak:put(#sr_group{group_host = {Group, Host},
opts = Opts},
sr_group_schema(),
[{'2i', [{<<"host">>, Host}]}])};
set_group_opts(Host, Group, Opts, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
SOpts = ejabberd_odbc:encode_term(Opts),
F = fun () ->
odbc_queries:update_t(<<"sr_group">>,
[<<"name">>, <<"opts">>], [SGroup, SOpts],
[<<"name='">>, SGroup, <<"'">>])
end,
ejabberd_odbc:sql_transaction(Host, F).
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:set_group_opts(Host, Group, Opts).
get_user_groups(US) ->
Host = element(2, US),
DBType = gen_mod:db_type(Host, ?MODULE),
get_user_groups(US, Host, DBType) ++
get_special_users_groups(Host).
get_user_groups(US, Host, mnesia) ->
case catch mnesia:dirty_read(sr_user, US) of
Rs when is_list(Rs) ->
[Group
|| #sr_user{group_host = {Group, H}} <- Rs, H == Host];
_ -> []
end;
get_user_groups(US, Host, riak) ->
case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
{ok, Rs} ->
[Group || #sr_user{group_host = {Group, H}} <- Rs, H == Host];
_ ->
[]
end;
get_user_groups(US, Host, odbc) ->
SJID = make_jid_s(US),
case catch ejabberd_odbc:sql_query(Host,
[<<"select grp from sr_user where jid='">>,
SJID, <<"';">>])
of
{selected, [<<"grp">>], Rs} -> [G || [G] <- Rs];
_ -> []
end.
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:get_user_groups(US, Host) ++ get_special_users_groups(Host).
is_group_enabled(Host1, Group1) ->
{Host, Group} = split_grouphost(Host1, Group1),
@ -630,39 +474,8 @@ get_group_users(Host, Group, GroupOpts) ->
++ get_group_explicit_users(Host, Group).
get_group_explicit_users(Host, Group) ->
get_group_explicit_users(Host, Group,
gen_mod:db_type(Host, ?MODULE)).
get_group_explicit_users(Host, Group, mnesia) ->
Read = (catch mnesia:dirty_index_read(sr_user,
{Group, Host}, #sr_user.group_host)),
case Read of
Rs when is_list(Rs) -> [R#sr_user.us || R <- Rs];
_ -> []
end;
get_group_explicit_users(Host, Group, riak) ->
case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
<<"group_host">>, {Group, Host}) of
{ok, Rs} ->
[R#sr_user.us || R <- Rs];
_ ->
[]
end;
get_group_explicit_users(Host, Group, odbc) ->
SGroup = ejabberd_odbc:escape(Group),
case catch ejabberd_odbc:sql_query(Host,
[<<"select jid from sr_user where grp='">>,
SGroup, <<"';">>])
of
{selected, [<<"jid">>], Rs} ->
lists:map(fun ([JID]) ->
{U, S, _} =
jid:tolower(jid:from_string(JID)),
{U, S}
end,
Rs);
_ -> []
end.
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:get_group_explicit_users(Host, Group).
get_group_name(Host1, Group1) ->
{Host, Group} = split_grouphost(Host1, Group1),
@ -718,44 +531,10 @@ get_special_displayed_groups(GroupsOpts) ->
%% for the list of groups of that server that user is member
%% get the list of groups displayed
get_user_displayed_groups(LUser, LServer, GroupsOpts) ->
Groups = get_user_displayed_groups(LUser, LServer,
GroupsOpts,
gen_mod:db_type(LServer, ?MODULE)),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Groups = Mod:get_user_displayed_groups(LUser, LServer, GroupsOpts),
displayed_groups(GroupsOpts, Groups).
get_user_displayed_groups(LUser, LServer, GroupsOpts,
mnesia) ->
case catch mnesia:dirty_read(sr_user, {LUser, LServer})
of
Rs when is_list(Rs) ->
[{Group, proplists:get_value(Group, GroupsOpts, [])}
|| #sr_user{group_host = {Group, H}} <- Rs,
H == LServer];
_ -> []
end;
get_user_displayed_groups(LUser, LServer, GroupsOpts,
riak) ->
case ejabberd_riak:get_by_index(sr_user, sr_user_schema(),
<<"us">>, {LUser, LServer}) of
{ok, Rs} ->
[{Group, proplists:get_value(Group, GroupsOpts, [])}
|| #sr_user{group_host = {Group, _}} <- Rs];
_ ->
[]
end;
get_user_displayed_groups(LUser, LServer, GroupsOpts,
odbc) ->
SJID = make_jid_s(LUser, LServer),
case catch ejabberd_odbc:sql_query(LServer,
[<<"select grp from sr_user where jid='">>,
SJID, <<"';">>])
of
{selected, [<<"grp">>], Rs} ->
[{Group, proplists:get_value(Group, GroupsOpts, [])}
|| [Group] <- Rs];
_ -> []
end.
%% @doc Get the list of groups that are displayed to this user
get_user_displayed_groups(US) ->
Host = element(2, US),
@ -779,42 +558,12 @@ get_user_displayed_groups(US) ->
is_group_enabled(Host, Group)].
is_user_in_group(US, Group, Host) ->
is_user_in_group(US, Group, Host,
gen_mod:db_type(Host, ?MODULE)).
is_user_in_group(US, Group, Host, mnesia) ->
case catch mnesia:dirty_match_object(#sr_user{us = US,
group_host = {Group, Host}})
of
[] -> lists:member(US, get_group_users(Host, Group));
_ -> true
end;
is_user_in_group(US, Group, Host, riak) ->
case ejabberd_riak:get_by_index(sr_user, sr_user_schema(), <<"us">>, US) of
{ok, Rs} ->
case lists:any(
fun(#sr_user{group_host = {G, H}}) ->
(Group == G) and (Host == H)
end, Rs) of
false ->
lists:member(US, get_group_users(Host, Group));
true ->
true
end;
_Err ->
false
end;
is_user_in_group(US, Group, Host, odbc) ->
SJID = make_jid_s(US),
SGroup = ejabberd_odbc:escape(Group),
case catch ejabberd_odbc:sql_query(Host,
[<<"select * from sr_user where jid='">>,
SJID, <<"' and grp='">>, SGroup,
<<"';">>])
of
{selected, _, []} ->
lists:member(US, get_group_users(Host, Group));
_ -> true
Mod = gen_mod:db_mod(Host, ?MODULE),
case Mod:is_user_in_group(US, Group, Host) of
false ->
lists:member(US, get_group_users(Host, Group));
true ->
true
end.
%% @spec (Host::string(), {User::string(), Server::string()}, Group::string()) -> {atomic, ok}
@ -837,31 +586,10 @@ add_user_to_group(Host, US, Group) ->
push_displayed_to_user(LUser, LServer, Host, both, DisplayedGroups),
broadcast_user_to_displayed(LUser, LServer, Host, both, DisplayedToGroups),
broadcast_displayed_to_user(LUser, LServer, Host, both, DisplayedGroups),
add_user_to_group(Host, US, Group, gen_mod:db_type(Host, ?MODULE))
Mod = gen_mod:db_mod(Host, ?MODULE),
Mod:add_user_to_group(Host, US, Group)
end.
add_user_to_group(Host, US, Group, mnesia) ->
R = #sr_user{us = US, group_host = {Group, Host}},
F = fun () -> mnesia:write(R) end,
mnesia:transaction(F);
add_user_to_group(Host, US, Group, riak) ->
{atomic, ejabberd_riak:put(
#sr_user{us = US, group_host = {Group, Host}},
sr_user_schema(),
[{i, {US, {Group, Host}}},
{'2i', [{<<"us">>, US},
{<<"group_host">>, {Group, Host}}]}])};
add_user_to_group(Host, US, Group, odbc) ->
SJID = make_jid_s(US),
SGroup = ejabberd_odbc:escape(Group),
F = fun () ->
odbc_queries:update_t(<<"sr_user">>,
[<<"jid">>, <<"grp">>], [SJID, SGroup],
[<<"jid='">>, SJID, <<"' and grp='">>,
SGroup, <<"'">>])
end,
ejabberd_odbc:sql_transaction(Host, F).
get_displayed_groups(Group, LServer) ->
GroupsOpts = groups_with_opts(LServer),
GroupOpts = proplists:get_value(Group, GroupsOpts, []),
@ -894,8 +622,8 @@ remove_user_from_group(Host, US, Group) ->
end,
(?MODULE):set_group_opts(Host, Group, NewGroupOpts);
nomatch ->
Result = remove_user_from_group(Host, US, Group,
gen_mod:db_type(Host, ?MODULE)),
Mod = gen_mod:db_mod(Host, ?MODULE),
Result = Mod:remove_user_from_group(Host, US, Group),
DisplayedToGroups = displayed_to_groups(Group, Host),
DisplayedGroups = get_displayed_groups(Group, LServer),
push_user_to_displayed(LUser, LServer, Group, Host, remove, DisplayedToGroups),
@ -903,23 +631,6 @@ remove_user_from_group(Host, US, Group) ->
Result
end.
remove_user_from_group(Host, US, Group, mnesia) ->
R = #sr_user{us = US, group_host = {Group, Host}},
F = fun () -> mnesia:delete_object(R) end,
mnesia:transaction(F);
remove_user_from_group(Host, US, Group, riak) ->
{atomic, ejabberd_riak:delete(sr_group, {US, {Group, Host}})};
remove_user_from_group(Host, US, Group, odbc) ->
SJID = make_jid_s(US),
SGroup = ejabberd_odbc:escape(Group),
F = fun () ->
ejabberd_odbc:sql_query_t([<<"delete from sr_user where jid='">>,
SJID, <<"' and grp='">>, SGroup,
<<"';">>]),
ok
end,
ejabberd_odbc:sql_transaction(Host, F).
push_members_to_user(LUser, LServer, Group, Host,
Subscription) ->
GroupsOpts = groups_with_opts(LServer),
@ -1385,13 +1096,6 @@ displayed_groups_update(Members, DisplayedGroups, Subscription) ->
end
end, Members).
make_jid_s(U, S) ->
ejabberd_odbc:escape(jid:to_string(jid:tolower(jid:make(U,
S,
<<"">>)))).
make_jid_s({U, S}) -> make_jid_s(U, S).
opts_to_binary(Opts) ->
lists:map(
fun({name, Name}) ->
@ -1404,105 +1108,17 @@ opts_to_binary(Opts) ->
Opt
end, Opts).
sr_group_schema() ->
{record_info(fields, sr_group), #sr_group{}}.
sr_user_schema() ->
{record_info(fields, sr_user), #sr_user{}}.
update_tables() ->
update_sr_group_table(),
update_sr_user_table().
update_sr_group_table() ->
Fields = record_info(fields, sr_group),
case mnesia:table_info(sr_group, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
sr_group, Fields, set,
fun(#sr_group{group_host = {G, _}}) -> G end,
fun(#sr_group{group_host = {G, H},
opts = Opts} = R) ->
R#sr_group{group_host = {iolist_to_binary(G),
iolist_to_binary(H)},
opts = opts_to_binary(Opts)}
end);
_ ->
?INFO_MSG("Recreating sr_group table", []),
mnesia:transform_table(sr_group, ignore, Fields)
end.
update_sr_user_table() ->
Fields = record_info(fields, sr_user),
case mnesia:table_info(sr_user, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
sr_user, Fields, bag,
fun(#sr_user{us = {U, _}}) -> U end,
fun(#sr_user{us = {U, S}, group_host = {G, H}} = R) ->
R#sr_user{us = {iolist_to_binary(U), iolist_to_binary(S)},
group_host = {iolist_to_binary(G),
iolist_to_binary(H)}}
end);
_ ->
?INFO_MSG("Recreating sr_user table", []),
mnesia:transform_table(sr_user, ignore, Fields)
end.
export(_Server) ->
[{sr_group,
fun(Host, #sr_group{group_host = {Group, LServer}, opts = Opts})
when LServer == Host ->
SGroup = ejabberd_odbc:escape(Group),
SOpts = ejabberd_odbc:encode_term(Opts),
[[<<"delete from sr_group where name='">>, Group, <<"';">>],
[<<"insert into sr_group(name, opts) values ('">>,
SGroup, <<"', '">>, SOpts, <<"');">>]];
(_Host, _R) ->
[]
end},
{sr_user,
fun(Host, #sr_user{us = {U, S}, group_host = {Group, LServer}})
when LServer == Host ->
SGroup = ejabberd_odbc:escape(Group),
SJID = ejabberd_odbc:escape(
jid:to_string(
jid:tolower(
jid:make(U, S, <<"">>)))),
[[<<"delete from sr_user where jid='">>, SJID,
<<"'and grp='">>, Group, <<"';">>],
[<<"insert into sr_user(jid, grp) values ('">>,
SJID, <<"', '">>, SGroup, <<"');">>]];
(_Host, _R) ->
[]
end}].
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
import(LServer) ->
[{<<"select name, opts from sr_group;">>,
fun([Group, SOpts]) ->
#sr_group{group_host = {Group, LServer},
opts = ejabberd_odbc:decode_term(SOpts)}
end},
{<<"select jid, grp from sr_user;">>,
fun([SJID, Group]) ->
#jid{luser = U, lserver = S} = jid:from_string(SJID),
#sr_user{us = {U, S}, group_host = {Group, LServer}}
end}].
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:import(LServer).
import(_LServer, mnesia, #sr_group{} = G) ->
mnesia:dirty_write(G);
import(_LServer, mnesia, #sr_user{} = U) ->
mnesia:dirty_write(U);
import(_LServer, riak, #sr_group{group_host = {_, Host}} = G) ->
ejabberd_riak:put(G, sr_group_schema(), [{'2i', [{<<"host">>, Host}]}]);
import(_LServer, riak, #sr_user{us = US, group_host = {Group, Host}} = User) ->
ejabberd_riak:put(User, sr_user_schema(),
[{i, {US, {Group, Host}}},
{'2i', [{<<"us">>, US},
{<<"group_host">>, {Group, Host}}]}]);
import(_, _, _) ->
pass.
import(LServer, DBType, Data) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, Data).
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
mod_opt_type(_) -> [db_type].

View File

@ -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.

View File

@ -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{}}.

View File

@ -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).

View File

@ -25,8 +25,6 @@
-module(mod_vcard).
-compile([{parse_transform, ejabberd_sql_pt}]).
-author('alexey@process-one.net').
-protocol({xep, 54, '1.2'}).
@ -35,54 +33,31 @@
-behaviour(gen_mod).
-export([start/2, init/3, stop/1, get_sm_features/5,
process_local_iq/3, process_sm_iq/3, reindex_vcards/0,
process_local_iq/3, process_sm_iq/3, string2lower/1,
remove_user/2, export/1, import/1, import/3,
mod_opt_type/1, set_vcard/3]).
mod_opt_type/1, set_vcard/3, make_vcard_search/4]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("ejabberd_sql_pt.hrl").
-include("jlib.hrl").
-include("mod_vcard.hrl").
-define(JUD_MATCHES, 30).
-record(vcard_search,
{us, user, luser, fn, lfn, family, lfamily, given,
lgiven, middle, lmiddle, nickname, lnickname, bday,
lbday, ctry, lctry, locality, llocality, email, lemail,
orgname, lorgname, orgunit, lorgunit}).
-record(vcard, {us = {<<"">>, <<"">>} :: {binary(), binary()} | binary(),
vcard = #xmlel{} :: xmlel()}).
-define(PROCNAME, ejabberd_mod_vcard).
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #vcard{} | #vcard_search{}) -> ok | pass.
-callback get_vcard(binary(), binary()) -> [xmlel()] | error.
-callback set_vcard(binary(), binary(),
xmlel(), #vcard_search{}) -> {atomic, any()}.
-callback search(binary(), [{binary(), [binary()]}], boolean(),
infinity | pos_integer()) -> [binary()].
-callback remove_user(binary(), binary()) -> {atomic, any()}.
start(Host, Opts) ->
case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(vcard,
[{disc_only_copies, [node()]},
{attributes, record_info(fields, vcard)}]),
mnesia:create_table(vcard_search,
[{disc_copies, [node()]},
{attributes,
record_info(fields, vcard_search)}]),
update_tables(),
mnesia:add_table_index(vcard_search, luser),
mnesia:add_table_index(vcard_search, lfn),
mnesia:add_table_index(vcard_search, lfamily),
mnesia:add_table_index(vcard_search, lgiven),
mnesia:add_table_index(vcard_search, lmiddle),
mnesia:add_table_index(vcard_search, lnickname),
mnesia:add_table_index(vcard_search, lbday),
mnesia:add_table_index(vcard_search, lctry),
mnesia:add_table_index(vcard_search, llocality),
mnesia:add_table_index(vcard_search, lemail),
mnesia:add_table_index(vcard_search, lorgname),
mnesia:add_table_index(vcard_search, lorgunit);
_ -> ok
end,
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
IQDisc = gen_mod:get_opt(iqdisc, Opts, fun gen_iq_handler:check_type/1,
@ -206,38 +181,10 @@ process_sm_iq(From, To,
end.
get_vcard(LUser, LServer) ->
get_vcard(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_vcard(LUser, LServer).
get_vcard(LUser, LServer, mnesia) ->
US = {LUser, LServer},
F = fun () -> mnesia:read({vcard, US}) end,
case mnesia:transaction(F) of
{atomic, Rs} ->
lists:map(fun (R) -> R#vcard.vcard end, Rs);
{aborted, _Reason} -> error
end;
get_vcard(LUser, LServer, odbc) ->
case catch odbc_queries:get_vcard(LServer, LUser) of
{selected, [{SVCARD}]} ->
case fxml_stream:parse_element(SVCARD) of
{error, _Reason} -> error;
VCARD -> [VCARD]
end;
{selected, []} -> [];
_ -> error
end;
get_vcard(LUser, LServer, riak) ->
case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of
{ok, R} ->
[R#vcard.vcard];
{error, notfound} ->
[];
_ ->
error
end.
set_vcard(User, LServer, VCARD) ->
make_vcard_search(User, LUser, LServer, VCARD) ->
FN = fxml:get_path_s(VCARD, [{elem, <<"FN">>}, cdata]),
Family = fxml:get_path_s(VCARD,
[{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
@ -266,7 +213,6 @@ set_vcard(User, LServer, VCARD) ->
<<"">> -> EMail2;
_ -> EMail1
end,
LUser = jid:nodeprep(User),
LFN = string2lower(FN),
LFamily = string2lower(Family),
LGiven = string2lower(Given),
@ -278,80 +224,42 @@ set_vcard(User, LServer, VCARD) ->
LEMail = string2lower(EMail),
LOrgName = string2lower(OrgName),
LOrgUnit = string2lower(OrgUnit),
if (LUser == error) ->
{error, badarg};
true ->
case gen_mod:db_type(LServer, ?MODULE) of
mnesia ->
US = {LUser, LServer},
F = fun () ->
mnesia:write(#vcard{us = US, vcard = VCARD}),
mnesia:write(#vcard_search{us = US,
user = {User, LServer},
luser = LUser, fn = FN,
lfn = LFN,
family = Family,
lfamily = LFamily,
given = Given,
lgiven = LGiven,
middle = Middle,
lmiddle = LMiddle,
nickname = Nickname,
lnickname = LNickname,
bday = BDay,
lbday = LBDay,
ctry = CTRY,
lctry = LCTRY,
locality = Locality,
llocality = LLocality,
email = EMail,
lemail = LEMail,
orgname = OrgName,
lorgname = LOrgName,
orgunit = OrgUnit,
lorgunit = LOrgUnit})
end,
mnesia:transaction(F);
riak ->
US = {LUser, LServer},
ejabberd_riak:put(#vcard{us = US, vcard = VCARD},
vcard_schema(),
[{'2i', [{<<"user">>, User},
{<<"luser">>, LUser},
{<<"fn">>, FN},
{<<"lfn">>, LFN},
{<<"family">>, Family},
{<<"lfamily">>, LFamily},
{<<"given">>, Given},
{<<"lgiven">>, LGiven},
{<<"middle">>, Middle},
{<<"lmiddle">>, LMiddle},
{<<"nickname">>, Nickname},
{<<"lnickname">>, LNickname},
{<<"bday">>, BDay},
{<<"lbday">>, LBDay},
{<<"ctry">>, CTRY},
{<<"lctry">>, LCTRY},
{<<"locality">>, Locality},
{<<"llocality">>, LLocality},
{<<"email">>, EMail},
{<<"lemail">>, LEMail},
{<<"orgname">>, OrgName},
{<<"lorgname">>, LOrgName},
{<<"orgunit">>, OrgUnit},
{<<"lorgunit">>, LOrgUnit}]}]);
odbc ->
SVCARD = fxml:element_to_binary(VCARD),
odbc_queries:set_vcard(LServer, LUser, BDay, CTRY,
EMail, FN, Family, Given, LBDay,
LCTRY, LEMail, LFN, LFamily,
LGiven, LLocality, LMiddle,
LNickname, LOrgName, LOrgUnit,
Locality, Middle, Nickname, OrgName,
OrgUnit, SVCARD, User)
end,
ejabberd_hooks:run(vcard_set, LServer,
[LUser, LServer, VCARD])
US = {LUser, LServer},
#vcard_search{us = US,
user = {User, LServer},
luser = LUser, fn = FN,
lfn = LFN,
family = Family,
lfamily = LFamily,
given = Given,
lgiven = LGiven,
middle = Middle,
lmiddle = LMiddle,
nickname = Nickname,
lnickname = LNickname,
bday = BDay,
lbday = LBDay,
ctry = CTRY,
lctry = LCTRY,
locality = Locality,
llocality = LLocality,
email = EMail,
lemail = LEMail,
orgname = OrgName,
lorgname = LOrgName,
orgunit = OrgUnit,
lorgunit = LOrgUnit}.
set_vcard(User, LServer, VCARD) ->
case jid:nodeprep(User) of
error ->
{error, badarg};
LUser ->
VCardSearch = make_vcard_search(User, LUser, LServer, VCARD),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_vcard(LUser, LServer, VCARD, VCardSearch),
ejabberd_hooks:run(vcard_set, LServer,
[LUser, LServer, VCARD])
end.
string2lower(String) ->
@ -655,500 +563,36 @@ record_to_item(_LServer, #vcard_search{} = R) ->
?FIELD(<<"orgunit">>, (R#vcard_search.orgunit))]}.
search(LServer, Data) ->
DBType = gen_mod:db_type(LServer, ?MODULE),
MatchSpec = make_matchspec(LServer, Data, DBType),
Mod = gen_mod:db_mod(LServer, ?MODULE),
AllowReturnAll = gen_mod:get_module_opt(LServer, ?MODULE, allow_return_all,
fun(B) when is_boolean(B) -> B end,
false),
search(LServer, MatchSpec, AllowReturnAll, DBType).
search(LServer, MatchSpec, AllowReturnAll, mnesia) ->
if (MatchSpec == #vcard_search{_ = '_'}) and
not AllowReturnAll ->
[];
true ->
case catch mnesia:dirty_select(vcard_search,
[{MatchSpec, [], ['$_']}])
of
{'EXIT', Reason} -> ?ERROR_MSG("~p", [Reason]), [];
Rs ->
case gen_mod:get_module_opt(LServer, ?MODULE, matches,
fun(infinity) -> infinity;
(I) when is_integer(I),
I>0 ->
I
end, ?JUD_MATCHES) of
infinity ->
Rs;
Val ->
lists:sublist(Rs, Val)
end
end
end;
search(LServer, MatchSpec, AllowReturnAll, odbc) ->
if (MatchSpec == <<"">>) and not AllowReturnAll -> [];
true ->
Limit = case gen_mod:get_module_opt(LServer, ?MODULE, matches,
fun(infinity) -> infinity;
(I) when is_integer(I),
I>0 ->
I
end, ?JUD_MATCHES) of
infinity ->
<<"">>;
Val ->
[<<" LIMIT ">>,
jlib:integer_to_binary(Val)]
end,
case catch ejabberd_odbc:sql_query(LServer,
[<<"select username, fn, family, given, "
"middle, nickname, bday, ctry, "
"locality, email, orgname, orgunit "
"from vcard_search ">>,
MatchSpec, Limit, <<";">>])
of
{selected,
[<<"username">>, <<"fn">>, <<"family">>, <<"given">>,
<<"middle">>, <<"nickname">>, <<"bday">>, <<"ctry">>,
<<"locality">>, <<"email">>, <<"orgname">>,
<<"orgunit">>],
Rs}
when is_list(Rs) ->
Rs;
Error -> ?ERROR_MSG("~p", [Error]), []
end
end;
search(_LServer, _MatchSpec, _AllowReturnAll, riak) ->
[].
make_matchspec(LServer, Data, mnesia) ->
GlobMatch = #vcard_search{_ = '_'},
Match = filter_fields(Data, GlobMatch, LServer, mnesia),
Match;
make_matchspec(LServer, Data, odbc) ->
filter_fields(Data, <<"">>, LServer, odbc);
make_matchspec(_LServer, _Data, riak) ->
[].
filter_fields([], Match, _LServer, mnesia) -> Match;
filter_fields([], Match, _LServer, odbc) ->
case Match of
<<"">> -> <<"">>;
_ -> [<<" where ">>, Match]
end;
filter_fields([{SVar, [Val]} | Ds], Match, LServer,
mnesia)
when is_binary(Val) and (Val /= <<"">>) ->
LVal = string2lower(Val),
NewMatch = case SVar of
<<"user">> ->
case gen_mod:get_module_opt(LServer, ?MODULE,
search_all_hosts,
fun(B) when is_boolean(B) ->
B
end, true)
of
true -> Match#vcard_search{luser = make_val(LVal)};
false ->
Host = find_my_host(LServer),
Match#vcard_search{us = {make_val(LVal), Host}}
end;
<<"fn">> -> Match#vcard_search{lfn = make_val(LVal)};
<<"last">> ->
Match#vcard_search{lfamily = make_val(LVal)};
<<"first">> ->
Match#vcard_search{lgiven = make_val(LVal)};
<<"middle">> ->
Match#vcard_search{lmiddle = make_val(LVal)};
<<"nick">> ->
Match#vcard_search{lnickname = make_val(LVal)};
<<"bday">> ->
Match#vcard_search{lbday = make_val(LVal)};
<<"ctry">> ->
Match#vcard_search{lctry = make_val(LVal)};
<<"locality">> ->
Match#vcard_search{llocality = make_val(LVal)};
<<"email">> ->
Match#vcard_search{lemail = make_val(LVal)};
<<"orgname">> ->
Match#vcard_search{lorgname = make_val(LVal)};
<<"orgunit">> ->
Match#vcard_search{lorgunit = make_val(LVal)};
_ -> Match
end,
filter_fields(Ds, NewMatch, LServer, mnesia);
filter_fields([{SVar, [Val]} | Ds], Match, LServer,
odbc)
when is_binary(Val) and (Val /= <<"">>) ->
LVal = string2lower(Val),
NewMatch = case SVar of
<<"user">> -> make_val(Match, <<"lusername">>, LVal);
<<"fn">> -> make_val(Match, <<"lfn">>, LVal);
<<"last">> -> make_val(Match, <<"lfamily">>, LVal);
<<"first">> -> make_val(Match, <<"lgiven">>, LVal);
<<"middle">> -> make_val(Match, <<"lmiddle">>, LVal);
<<"nick">> -> make_val(Match, <<"lnickname">>, LVal);
<<"bday">> -> make_val(Match, <<"lbday">>, LVal);
<<"ctry">> -> make_val(Match, <<"lctry">>, LVal);
<<"locality">> ->
make_val(Match, <<"llocality">>, LVal);
<<"email">> -> make_val(Match, <<"lemail">>, LVal);
<<"orgname">> -> make_val(Match, <<"lorgname">>, LVal);
<<"orgunit">> -> make_val(Match, <<"lorgunit">>, LVal);
_ -> Match
end,
filter_fields(Ds, NewMatch, LServer, odbc);
filter_fields([_ | Ds], Match, LServer, DBType) ->
filter_fields(Ds, Match, LServer, DBType).
make_val(Match, Field, Val) ->
Condition = case str:suffix(<<"*">>, Val) of
true ->
Val1 = str:substr(Val, 1, byte_size(Val) - 1),
SVal = <<(ejabberd_odbc:escape_like(Val1))/binary,
"%">>,
[Field, <<" LIKE '">>, SVal, <<"'">>];
_ ->
SVal = ejabberd_odbc:escape(Val),
[Field, <<" = '">>, SVal, <<"'">>]
end,
case Match of
<<"">> -> Condition;
_ -> [Match, <<" and ">>, Condition]
end.
make_val(Val) ->
case str:suffix(<<"*">>, Val) of
true -> [str:substr(Val, 1, byte_size(Val) - 1)] ++ '_';
_ -> Val
end.
find_my_host(LServer) ->
Parts = str:tokens(LServer, <<".">>),
find_my_host(Parts, ?MYHOSTS).
find_my_host([], _Hosts) -> ?MYNAME;
find_my_host([_ | Tail] = Parts, Hosts) ->
Domain = parts_to_string(Parts),
case lists:member(Domain, Hosts) of
true -> Domain;
false -> find_my_host(Tail, Hosts)
end.
parts_to_string(Parts) ->
str:strip(list_to_binary(
lists:map(fun (S) -> <<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)).
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_user(LUser, LServer).
remove_user(LUser, LServer, mnesia) ->
US = {LUser, LServer},
F = fun () ->
mnesia:delete({vcard, US}),
mnesia:delete({vcard_search, US})
end,
mnesia:transaction(F);
remove_user(LUser, LServer, odbc) ->
ejabberd_odbc:sql_transaction(
LServer,
fun() ->
ejabberd_odbc:sql_query_t(
?SQL("delete from vcard where username=%(LUser)s")),
ejabberd_odbc:sql_query_t(
?SQL("delete from vcard_search where lusername=%(LUser)s"))
end);
remove_user(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete(vcard, {LUser, LServer})}.
update_tables() ->
update_vcard_table(),
update_vcard_search_table().
update_vcard_table() ->
Fields = record_info(fields, vcard),
case mnesia:table_info(vcard, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
vcard, Fields, set,
fun(#vcard{us = {U, _}}) -> U end,
fun(#vcard{us = {U, S}, vcard = El} = R) ->
R#vcard{us = {iolist_to_binary(U),
iolist_to_binary(S)},
vcard = fxml:to_xmlel(El)}
end);
_ ->
?INFO_MSG("Recreating vcard table", []),
mnesia:transform_table(vcard, ignore, Fields)
end.
update_vcard_search_table() ->
Fields = record_info(fields, vcard_search),
case mnesia:table_info(vcard_search, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
vcard_search, Fields, set,
fun(#vcard_search{us = {U, _}}) -> U end,
fun(#vcard_search{} = VS) ->
[vcard_search | L] = tuple_to_list(VS),
NewL = lists:map(
fun({U, S}) ->
{iolist_to_binary(U),
iolist_to_binary(S)};
(Str) ->
iolist_to_binary(Str)
end, L),
list_to_tuple([vcard_search | NewL])
end);
_ ->
?INFO_MSG("Recreating vcard_search table", []),
mnesia:transform_table(vcard_search, ignore, Fields)
end.
vcard_schema() ->
{record_info(fields, vcard), #vcard{}}.
export(_Server) ->
[{vcard,
fun(Host, #vcard{us = {LUser, LServer}, vcard = VCARD})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
SVCARD =
ejabberd_odbc:escape(fxml:element_to_binary(VCARD)),
[[<<"delete from vcard where username='">>, Username, <<"';">>],
[<<"insert into vcard(username, vcard) values ('">>,
Username, <<"', '">>, SVCARD, <<"');">>]];
(_Host, _R) ->
[]
end},
{vcard_search,
fun(Host, #vcard_search{user = {User, LServer}, luser = LUser,
fn = FN, lfn = LFN, family = Family,
lfamily = LFamily, given = Given,
lgiven = LGiven, middle = Middle,
lmiddle = LMiddle, nickname = Nickname,
lnickname = LNickname, bday = BDay,
lbday = LBDay, ctry = CTRY, lctry = LCTRY,
locality = Locality, llocality = LLocality,
email = EMail, lemail = LEMail,
orgname = OrgName, lorgname = LOrgName,
orgunit = OrgUnit, lorgunit = LOrgUnit})
when LServer == Host ->
Username = ejabberd_odbc:escape(User),
LUsername = ejabberd_odbc:escape(LUser),
SFN = ejabberd_odbc:escape(FN),
SLFN = ejabberd_odbc:escape(LFN),
SFamily = ejabberd_odbc:escape(Family),
SLFamily = ejabberd_odbc:escape(LFamily),
SGiven = ejabberd_odbc:escape(Given),
SLGiven = ejabberd_odbc:escape(LGiven),
SMiddle = ejabberd_odbc:escape(Middle),
SLMiddle = ejabberd_odbc:escape(LMiddle),
SNickname = ejabberd_odbc:escape(Nickname),
SLNickname = ejabberd_odbc:escape(LNickname),
SBDay = ejabberd_odbc:escape(BDay),
SLBDay = ejabberd_odbc:escape(LBDay),
SCTRY = ejabberd_odbc:escape(CTRY),
SLCTRY = ejabberd_odbc:escape(LCTRY),
SLocality = ejabberd_odbc:escape(Locality),
SLLocality = ejabberd_odbc:escape(LLocality),
SEMail = ejabberd_odbc:escape(EMail),
SLEMail = ejabberd_odbc:escape(LEMail),
SOrgName = ejabberd_odbc:escape(OrgName),
SLOrgName = ejabberd_odbc:escape(LOrgName),
SOrgUnit = ejabberd_odbc:escape(OrgUnit),
SLOrgUnit = ejabberd_odbc:escape(LOrgUnit),
[[<<"delete from vcard_search where lusername='">>,
LUsername, <<"';">>],
[<<"insert into vcard_search( username, "
"lusername, fn, lfn, family, lfamily, "
" given, lgiven, middle, lmiddle, "
"nickname, lnickname, bday, lbday, "
"ctry, lctry, locality, llocality, "
" email, lemail, orgname, lorgname, "
"orgunit, lorgunit)values (">>,
<<" '">>, Username, <<"', '">>, LUsername,
<<"', '">>, SFN, <<"', '">>, SLFN,
<<"', '">>, SFamily, <<"', '">>, SLFamily,
<<"', '">>, SGiven, <<"', '">>, SLGiven,
<<"', '">>, SMiddle, <<"', '">>, SLMiddle,
<<"', '">>, SNickname, <<"', '">>, SLNickname,
<<"', '">>, SBDay, <<"', '">>, SLBDay,
<<"', '">>, SCTRY, <<"', '">>, SLCTRY,
<<"', '">>, SLocality, <<"', '">>, SLLocality,
<<"', '">>, SEMail, <<"', '">>, SLEMail,
<<"', '">>, SOrgName, <<"', '">>, SLOrgName,
<<"', '">>, SOrgUnit, <<"', '">>, SLOrgUnit,
<<"');">>]];
(_Host, _R) ->
[]
end}].
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
import(LServer) ->
[{<<"select username, vcard from vcard;">>,
fun([LUser, SVCard]) ->
#xmlel{} = VCARD = fxml_stream:parse_element(SVCard),
#vcard{us = {LUser, LServer}, vcard = VCARD}
end},
{<<"select username, lusername, fn, lfn, family, lfamily, "
"given, lgiven, middle, lmiddle, nickname, lnickname, "
"bday, lbday, ctry, lctry, locality, llocality, email, "
"lemail, orgname, lorgname, orgunit, lorgunit from vcard_search;">>,
fun([User, LUser, FN, LFN,
Family, LFamily, Given, LGiven,
Middle, LMiddle, Nickname, LNickname,
BDay, LBDay, CTRY, LCTRY, Locality, LLocality,
EMail, LEMail, OrgName, LOrgName, OrgUnit, LOrgUnit]) ->
#vcard_search{us = {LUser, LServer},
user = {User, LServer}, luser = LUser,
fn = FN, lfn = LFN, family = Family,
lfamily = LFamily, given = Given,
lgiven = LGiven, middle = Middle,
lmiddle = LMiddle, nickname = Nickname,
lnickname = LNickname, bday = BDay,
lbday = LBDay, ctry = CTRY, lctry = LCTRY,
locality = Locality, llocality = LLocality,
email = EMail, lemail = LEMail,
orgname = OrgName, lorgname = LOrgName,
orgunit = OrgUnit, lorgunit = LOrgUnit}
end}].
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:import(LServer).
import(_LServer, mnesia, #vcard{} = VCard) ->
mnesia:dirty_write(VCard);
import(_LServer, mnesia, #vcard_search{} = S) ->
mnesia:dirty_write(S);
import(_LServer, riak, #vcard{us = {LUser, _}, vcard = El} = VCard) ->
FN = fxml:get_path_s(El, [{elem, <<"FN">>}, cdata]),
Family = fxml:get_path_s(El,
[{elem, <<"N">>}, {elem, <<"FAMILY">>}, cdata]),
Given = fxml:get_path_s(El,
[{elem, <<"N">>}, {elem, <<"GIVEN">>}, cdata]),
Middle = fxml:get_path_s(El,
[{elem, <<"N">>}, {elem, <<"MIDDLE">>}, cdata]),
Nickname = fxml:get_path_s(El,
[{elem, <<"NICKNAME">>}, cdata]),
BDay = fxml:get_path_s(El,
[{elem, <<"BDAY">>}, cdata]),
CTRY = fxml:get_path_s(El,
[{elem, <<"ADR">>}, {elem, <<"CTRY">>}, cdata]),
Locality = fxml:get_path_s(El,
[{elem, <<"ADR">>}, {elem, <<"LOCALITY">>},
cdata]),
EMail1 = fxml:get_path_s(El,
[{elem, <<"EMAIL">>}, {elem, <<"USERID">>}, cdata]),
EMail2 = fxml:get_path_s(El,
[{elem, <<"EMAIL">>}, cdata]),
OrgName = fxml:get_path_s(El,
[{elem, <<"ORG">>}, {elem, <<"ORGNAME">>}, cdata]),
OrgUnit = fxml:get_path_s(El,
[{elem, <<"ORG">>}, {elem, <<"ORGUNIT">>}, cdata]),
EMail = case EMail1 of
<<"">> -> EMail2;
_ -> EMail1
end,
LFN = string2lower(FN),
LFamily = string2lower(Family),
LGiven = string2lower(Given),
LMiddle = string2lower(Middle),
LNickname = string2lower(Nickname),
LBDay = string2lower(BDay),
LCTRY = string2lower(CTRY),
LLocality = string2lower(Locality),
LEMail = string2lower(EMail),
LOrgName = string2lower(OrgName),
LOrgUnit = string2lower(OrgUnit),
ejabberd_riak:put(VCard, vcard_schema(),
[{'2i', [{<<"user">>, LUser},
{<<"luser">>, LUser},
{<<"fn">>, FN},
{<<"lfn">>, LFN},
{<<"family">>, Family},
{<<"lfamily">>, LFamily},
{<<"given">>, Given},
{<<"lgiven">>, LGiven},
{<<"middle">>, Middle},
{<<"lmiddle">>, LMiddle},
{<<"nickname">>, Nickname},
{<<"lnickname">>, LNickname},
{<<"bday">>, BDay},
{<<"lbday">>, LBDay},
{<<"ctry">>, CTRY},
{<<"lctry">>, LCTRY},
{<<"locality">>, Locality},
{<<"llocality">>, LLocality},
{<<"email">>, EMail},
{<<"lemail">>, LEMail},
{<<"orgname">>, OrgName},
{<<"lorgname">>, LOrgName},
{<<"orgunit">>, OrgUnit},
{<<"lorgunit">>, LOrgUnit}]}]);
import(_LServer, riak, #vcard_search{}) ->
ok;
import(_, _, _) ->
pass.
import(LServer, DBType, VCard) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, VCard).
mod_opt_type(allow_return_all) ->
fun (B) when is_boolean(B) -> B end;

213
src/mod_vcard_mnesia.erl Normal file
View File

@ -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, $.).

151
src/mod_vcard_riak.erl Normal file
View File

@ -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{}}.

268
src/mod_vcard_sql.erl Normal file
View File

@ -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.

View File

@ -17,26 +17,22 @@
-include("ejabberd.hrl").
-include("logger.hrl").
-include("mod_vcard_xupdate.hrl").
-include("jlib.hrl").
-record(vcard_xupdate, {us = {<<>>, <<>>} :: {binary(), binary()},
hash = <<>> :: binary()}).
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #vcard_xupdate{}) -> ok | pass.
-callback add_xupdate(binary(), binary(), binary()) -> {atomic, any()}.
-callback get_xupdate(binary(), binary()) -> binary() | undefined.
-callback remove_xupdate(binary(), binary()) -> {atomic, any()}.
%%====================================================================
%% gen_mod callbacks
%%====================================================================
start(Host, Opts) ->
case gen_mod:db_type(Host, Opts) of
mnesia ->
mnesia:create_table(vcard_xupdate,
[{disc_copies, [node()]},
{attributes,
record_info(fields, vcard_xupdate)}]),
update_table();
_ -> ok
end,
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
ejabberd_hooks:add(c2s_update_presence, Host, ?MODULE,
update_presence, 100),
ejabberd_hooks:add(vcard_set, Host, ?MODULE, vcard_set,
@ -79,75 +75,16 @@ vcard_set(LUser, LServer, VCARD) ->
%%====================================================================
add_xupdate(LUser, LServer, Hash) ->
add_xupdate(LUser, LServer, Hash,
gen_mod:db_type(LServer, ?MODULE)).
add_xupdate(LUser, LServer, Hash, mnesia) ->
F = fun () ->
mnesia:write(#vcard_xupdate{us = {LUser, LServer},
hash = Hash})
end,
mnesia:transaction(F);
add_xupdate(LUser, LServer, Hash, riak) ->
{atomic, ejabberd_riak:put(#vcard_xupdate{us = {LUser, LServer},
hash = Hash},
vcard_xupdate_schema())};
add_xupdate(LUser, LServer, Hash, odbc) ->
Username = ejabberd_odbc:escape(LUser),
SHash = ejabberd_odbc:escape(Hash),
F = fun () ->
odbc_queries:update_t(<<"vcard_xupdate">>,
[<<"username">>, <<"hash">>],
[Username, SHash],
[<<"username='">>, Username, <<"'">>])
end,
ejabberd_odbc:sql_transaction(LServer, F).
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:add_xupdate(LUser, LServer, Hash).
get_xupdate(LUser, LServer) ->
get_xupdate(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
get_xupdate(LUser, LServer, mnesia) ->
case mnesia:dirty_read(vcard_xupdate, {LUser, LServer})
of
[#vcard_xupdate{hash = Hash}] -> Hash;
_ -> undefined
end;
get_xupdate(LUser, LServer, riak) ->
case ejabberd_riak:get(vcard_xupdate, vcard_xupdate_schema(),
{LUser, LServer}) of
{ok, #vcard_xupdate{hash = Hash}} -> Hash;
_ -> undefined
end;
get_xupdate(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
case ejabberd_odbc:sql_query(LServer,
[<<"select hash from vcard_xupdate where "
"username='">>,
Username, <<"';">>])
of
{selected, [<<"hash">>], [[Hash]]} -> Hash;
_ -> undefined
end.
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:get_xupdate(LUser, LServer).
remove_xupdate(LUser, LServer) ->
remove_xupdate(LUser, LServer,
gen_mod:db_type(LServer, ?MODULE)).
remove_xupdate(LUser, LServer, mnesia) ->
F = fun () ->
mnesia:delete({vcard_xupdate, {LUser, LServer}})
end,
mnesia:transaction(F);
remove_xupdate(LUser, LServer, riak) ->
{atomic, ejabberd_riak:delete(vcard_xupdate, {LUser, LServer})};
remove_xupdate(LUser, LServer, odbc) ->
Username = ejabberd_odbc:escape(LUser),
F = fun () ->
ejabberd_odbc:sql_query_t([<<"delete from vcard_xupdate where username='">>,
Username, <<"';">>])
end,
ejabberd_odbc:sql_transaction(LServer, F).
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:remove_xupdate(LUser, LServer).
%%%----------------------------------------------------------------------
%%% Presence stanza rebuilding
@ -184,53 +121,17 @@ build_xphotoel(User, Host) ->
attrs = [{<<"xmlns">>, ?NS_VCARD_UPDATE}],
children = PhotoEl}.
vcard_xupdate_schema() ->
{record_info(fields, vcard_xupdate), #vcard_xupdate{}}.
update_table() ->
Fields = record_info(fields, vcard_xupdate),
case mnesia:table_info(vcard_xupdate, attributes) of
Fields ->
ejabberd_config:convert_table_to_binary(
vcard_xupdate, Fields, set,
fun(#vcard_xupdate{us = {U, _}}) -> U end,
fun(#vcard_xupdate{us = {U, S}, hash = Hash} = R) ->
R#vcard_xupdate{us = {iolist_to_binary(U),
iolist_to_binary(S)},
hash = iolist_to_binary(Hash)}
end);
_ ->
?INFO_MSG("Recreating vcard_xupdate table", []),
mnesia:transform_table(vcard_xupdate, ignore, Fields)
end.
export(_Server) ->
[{vcard_xupdate,
fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash})
when LServer == Host ->
Username = ejabberd_odbc:escape(LUser),
SHash = ejabberd_odbc:escape(Hash),
[[<<"delete from vcard_xupdate where username='">>,
Username, <<"';">>],
[<<"insert into vcard_xupdate(username, "
"hash) values ('">>,
Username, <<"', '">>, SHash, <<"');">>]];
(_Host, _R) ->
[]
end}].
export(LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:export(LServer).
import(LServer) ->
[{<<"select username, hash from vcard_xupdate;">>,
fun([LUser, Hash]) ->
#vcard_xupdate{us = {LUser, LServer}, hash = Hash}
end}].
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:import(LServer).
import(_LServer, mnesia, #vcard_xupdate{} = R) ->
mnesia:dirty_write(R);
import(_LServer, riak, #vcard_xupdate{} = R) ->
ejabberd_riak:put(R, vcard_xupdate_schema());
import(_, _, _) ->
pass.
import(LServer, DBType, LA) ->
Mod = gen_mod:db_mod(DBType, ?MODULE),
Mod:import(LServer, LA).
mod_opt_type(db_type) -> fun gen_mod:v_db/1;
mod_opt_type(_) -> [db_type].

View File

@ -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.

View File

@ -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{}}.

View File

@ -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
%%%===================================================================