25
1
mirror of https://github.com/processone/ejabberd.git synced 2024-11-30 16:36:29 +01:00

Fix ejabberd modules

This commit is contained in:
Badlop 2010-07-22 18:49:12 +02:00
parent 58bed2cbff
commit 597c1c87d4
9 changed files with 1043 additions and 1217 deletions

View File

@ -2,10 +2,10 @@
%%% File : ejabberd_auth_storage.erl
%%% Author : Alexey Shchepin <alexey@process-one.net>, Stephan Maka
%%% Purpose : Authentification via gen_storage
%%% Created : 16 Sep 2008 Stephan Maka
%%% Created : 12 Dec 2004 by Alexey Shchepin <alexey@process-one.net>
%%%
%%%
%%% ejabberd, Copyright (C) 2002-2008 ProcessOne
%%% ejabberd, Copyright (C) 2002-2010 ProcessOne
%%%
%%% This program is free software; you can redistribute it and/or
%%% modify it under the terms of the GNU General Public License as
@ -24,6 +24,31 @@
%%%
%%%----------------------------------------------------------------------
%%% Database schema (version / storage / table)
%%%
%%% 2.1.x / mnesia / passwd
%%% us = {Username::string(), Host::string()}
%%% password = string()
%%%
%%% 2.1.x / odbc / users
%%% username = varchar250
%%% password = text
%%%
%%% 3.0.0-prealpha / mnesia / passwd
%%% Same as 2.1.x
%%%
%%% 3.0.0-prealpha / odbc / users
%%% Same as 2.1.x
%%%
%%% 3.0.0-alpha / mnesia / passwd
%%% user_host = {Username::string(), Host::string()}
%%% password = string()
%%%
%%% 3.0.0-alpha / odbc / passwd
%%% user = varchar150
%%% host = varchar150
%%% password = text
-module(ejabberd_auth_storage).
-author('alexey@process-one.net').
@ -48,32 +73,59 @@
-include("ejabberd.hrl").
-record(passwd, {us, password}).
-record(passwd, {user_host, password}).
-record(reg_users_counter, {vhost, count}).
%%%----------------------------------------------------------------------
%%% API
%%%----------------------------------------------------------------------
%% @spec (Host) -> ok
%% Host = string()
start(Host) ->
Backend =
case ejabberd_config:get_local_option({auth_storage, Host}) of
undefined -> mnesia;
B -> B
end,
gen_storage:create_table(Backend, Host, passwd,
HostB = list_to_binary(Host),
gen_storage:create_table(Backend, HostB, passwd,
[{odbc_host, Host},
{disc_copies, [node()]},
{attributes, record_info(fields, passwd)},
{types, [{us, {text, text}}]}
{types, [{user_host, {text, text}}]}
]),
update_table(Host),
update_table(Host, Backend),
mnesia:create_table(reg_users_counter,
[{ram_copies, [node()]},
{attributes, record_info(fields, reg_users_counter)}]),
update_reg_users_counter_table(Host),
ok.
update_reg_users_counter_table(Server) ->
Set = get_vh_registered_users(Server),
Size = length(Set),
LServer = exmpp_jid:prep_domain(exmpp_jid:parse(Server)),
F = fun() ->
mnesia:write(#reg_users_counter{vhost = LServer,
count = Size})
end,
mnesia:sync_dirty(F).
%% @spec () -> bool()
plain_password_required() ->
false.
%% @spec (User, Server, Password) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
check_password(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
case catch gen_storage:dirty_read(LServer, {passwd, US}) of
[#passwd{password = Password}] ->
@ -82,15 +134,22 @@ check_password(User, Server, Password) ->
false
end.
check_password(User, Server, Password, StreamID, Digest) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
%% @spec (User, Server, Password, Digest, DigestGen) -> bool()
%% User = string()
%% Server = string()
%% Password = string()
%% Digest = string()
%% DigestGen = function()
check_password(User, Server, Password, Digest, DigestGen) ->
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
case catch gen_storage:dirty_read(LServer, {passwd, US}) of
[#passwd{password = Passwd}] ->
DigRes = if
Digest /= "" ->
Digest == sha:sha(StreamID ++ Passwd);
Digest == DigestGen(Passwd);
true ->
false
end,
@ -103,111 +162,169 @@ check_password(User, Server, Password, StreamID, Digest) ->
false
end.
%% @spec (User::string(), Server::string(), Password::string()) ->
%% ok | {error, invalid_jid}
%% @spec (User, Server, Password) -> ok | {error, invalid_jid}
%% User = string()
%% Server = string()
%% Password = string()
set_password(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
if
(LUser == error) or (LServer == error) ->
LUser = (catch exmpp_stringprep:nodeprep(User)),
LServer = (catch exmpp_stringprep:nameprep(Server)),
case {LUser, LServer} of
{{stringprep, _, invalid_string, _}, _} ->
{error, invalid_jid};
true ->
{_, {stringprep, _, invalid_string, _}} ->
{error, invalid_jid};
US ->
%% TODO: why is this a transaction?
F = fun() ->
gen_storage:write(LServer,
#passwd{us = US,
#passwd{user_host = US,
password = Password})
end,
{atomic, ok} = gen_storage:transaction(LServer, passwd, F),
ok
end.
%% @spec (User, Server, Password) -> {atomic, ok} | {atomic, exists} | {error, invalid_jid} | {aborted, Reason}
%% User = string()
%% Server = string()
%% Password = string()
try_register(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
if
(LUser == error) or (LServer == error) ->
LUser = (catch exmpp_stringprep:nodeprep(User)),
LServer = (catch exmpp_stringprep:nameprep(Server)),
case {LUser, LServer} of
{{stringprep, _, invalid_string, _}, _} ->
{error, invalid_jid};
true ->
{_, {stringprep, _, invalid_string, _}} ->
{error, invalid_jid};
US ->
F = fun() ->
case gen_storage:read(LServer, {passwd, US}) of
[] ->
gen_storage:write(LServer,
#passwd{us = US,
#passwd{user_host = US,
password = Password}),
mnesia:dirty_update_counter(
reg_users_counter,
exmpp_jid:prep_domain(exmpp_jid:parse(Server)), 1),
ok;
[_E] ->
exists
end
end,
%% TODO: transaction retval?
%% TODO: transaction return value?
gen_storage:transaction(LServer, passwd, F)
end.
%% Get all registered users in Mnesia
%% @spec () -> [{LUser, LServer}]
%% LUser = string()
%% LServer = string()
%% @doc Get all registered users in Mnesia.
dirty_get_registered_users() ->
%% TODO:
exit(not_implemented).
%% @spec (Server) -> [{LUser, LServer}]
%% Server = string()
%% LUser = string()
%% LServer = string()
get_vh_registered_users(Server) ->
LServer = jlib:nameprep(Server),
lists:map(fun(#passwd{us = US}) ->
LServer = exmpp_stringprep:nameprep(Server),
lists:map(fun(#passwd{user_host = US}) ->
US
end,
gen_storage:dirty_select(LServer, passwd,
[{'=', us, {'_', LServer}}])).
[{'=', user_host, {'_', LServer}}])).
%% @spec (Server, Opts) -> [{LUser, LServer}]
%% Server = string()
%% Opts = [{Opt, Val}]
%% Opt = atom()
%% Val = term()
%% LUser = string()
%% LServer = string()
%% @doc Return the registered users for the specified host.
%%
%% `Opts' can be one of the following:
%% <ul>
%% <li>`[{from, integer()}, {to, integer()}]'</li>
%% <li>`[{limit, integer()}, {offset, integer()}]'</li>
%% <li>`[{prefix, string()}]'</li>
%% <li>`[{prefix, string()}, {from, integer()}, {to, integer()}]'</li>
%% <li>`[{prefix, string()}, {limit, integer()}, {offset, integer()}]'</li>
%% </ul>
get_vh_registered_users(Server, [{from, Start}, {to, End}])
when is_integer(Start) and is_integer(End) ->
when is_integer(Start) and is_integer(End) ->
get_vh_registered_users(Server, [{limit, End-Start+1}, {offset, Start}]);
get_vh_registered_users(Server, [{limit, Limit}, {offset, Offset}])
when is_integer(Limit) and is_integer(Offset) ->
when is_integer(Limit) and is_integer(Offset) ->
case get_vh_registered_users(Server) of
[] ->
[];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
[] ->
[];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
end;
get_vh_registered_users(Server, [{prefix, Prefix}])
when is_list(Prefix) ->
when is_list(Prefix) ->
Set = [{U,S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
lists:keysort(1, Set);
get_vh_registered_users(Server, [{prefix, Prefix}, {from, Start}, {to, End}])
when is_list(Prefix) and is_integer(Start) and is_integer(End) ->
when is_list(Prefix) and is_integer(Start) and is_integer(End) ->
get_vh_registered_users(Server, [{prefix, Prefix}, {limit, End-Start+1}, {offset, Start}]);
get_vh_registered_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, Offset}])
when is_list(Prefix) and is_integer(Limit) and is_integer(Offset) ->
when is_list(Prefix) and is_integer(Limit) and is_integer(Offset) ->
case [{U,S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)] of
[] ->
[];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
[] ->
[];
Users ->
Set = lists:keysort(1, Users),
L = length(Set),
Start = if Offset < 1 -> 1;
Offset > L -> L;
true -> Offset
end,
lists:sublist(Set, Start, Limit)
end;
get_vh_registered_users(Server, _) ->
get_vh_registered_users(Server).
%% @spec (Server) -> Users_Number
%% Server = string()
%% Users_Number = integer()
get_vh_registered_users_number(Server) ->
Set = get_vh_registered_users(Server),
length(Set).
LServer = exmpp_jid:prep_domain(exmpp_jid:parse(Server)),
Query = mnesia:dirty_select(
reg_users_counter,
[{#reg_users_counter{vhost = LServer, count = '$1'},
[],
['$1']}]),
case Query of
[Count] ->
Count;
_ -> 0
end.
%% @spec (Server, [{prefix, Prefix}]) -> Users_Number
%% Server = string()
%% Prefix = string()
%% Users_Number = integer()
get_vh_registered_users_number(Server, [{prefix, Prefix}]) when is_list(Prefix) ->
Set = [{U, S} || {U, S} <- get_vh_registered_users(Server), lists:prefix(Prefix, U)],
@ -216,88 +333,145 @@ get_vh_registered_users_number(Server, [{prefix, Prefix}]) when is_list(Prefix)
get_vh_registered_users_number(Server, _) ->
get_vh_registered_users_number(Server).
%% @spec (User, Server) -> Password | false
%% User = string()
%% Server = string()
%% Password = string()
get_password(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch gen_storage:dirty_read(LServer, passwd, US) of
[#passwd{password = Password}] ->
Password;
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
case catch gen_storage:dirty_read(LServer, passwd, US) of
[#passwd{password = Password}] ->
Password;
_ ->
false
end
catch
_ ->
false
end.
%% @spec (User, Server) -> Password | nil()
%% User = string()
%% Server = string()
%% Password = string()
get_password_s(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch gen_storage:dirty_read(LServer, passwd, US) of
[#passwd{password = Password}] ->
Password;
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
case catch gen_storage:dirty_read(LServer, passwd, US) of
[#passwd{password = Password}] ->
Password;
_ ->
[]
end
catch
_ ->
[]
end.
%% @spec (User, Server) -> true | false | {error, Error}
%% User = string()
%% Server = string()
is_user_exists(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
case catch gen_storage:dirty_read(LServer, {passwd, US}) of
[] ->
false;
[_] ->
true;
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
case catch gen_storage:dirty_read(LServer, {passwd, US}) of
[] ->
false;
[_] ->
true;
Other ->
{error, Other}
end
catch
_ ->
false
end.
%% @spec (User, Server) -> ok
%% User = string()
%% Server = string()
%% @doc Remove user.
%% Note: it returns ok even if there was some problem removing the user.
remove_user(User, Server) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
gen_storage:delete(LServer, {passwd, US})
end,
gen_storage:transaction(LServer, passwd, F),
ejabberd_hooks:run(remove_user, LServer, [User, Server]).
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
gen_storage:delete(LServer, {passwd, US}),
mnesia:dirty_update_counter(reg_users_counter,
exmpp_jid:prep_domain(exmpp_jid:parse(Server)), -1)
end,
gen_storage:transaction(LServer, passwd, F),
ok
catch
_ ->
ok
end.
%% @spec (User, Server, Password) -> ok | not_exists | not_allowed | bad_request
%% User = string()
%% Server = string()
%% Password = string()
%% @doc Remove user if the provided password is correct.
remove_user(User, Server, Password) ->
LUser = jlib:nodeprep(User),
LServer = jlib:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
case gen_storage:read(LServer, {passwd, US}) of
[#passwd{password = Password}] ->
gen_storage:delete(LServer, {passwd, US}),
ok;
[_] ->
not_allowed;
_ ->
not_exists
end
end,
case gen_storage:transaction(LServer, passwd, F) of
{atomic, ok} ->
ejabberd_hooks:run(remove_user, LServer, [User, Server]),
ok;
{atomic, Res} ->
Res;
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
US = {LUser, LServer},
F = fun() ->
case gen_storage:read(LServer, {passwd, US}) of
[#passwd{password = Password}] ->
gen_storage:delete(LServer, {passwd, US}),
mnesia:dirty_update_counter(reg_users_counter,
exmpp_jid:prep_domain(exmpp_jid:parse(Server)), -1),
ok;
[_] ->
not_allowed;
_ ->
not_exists
end
end,
case gen_storage:transaction(LServer, passwd, F) of
{atomic, ok} ->
ok;
{atomic, Res} ->
Res;
_ ->
bad_request
end
catch
_ ->
bad_request
end.
update_table(Host) ->
%% @spec () -> term()
update_table(Host, mnesia) ->
gen_storage_migration:migrate_mnesia(
Host, passwd,
[{passwd, [user, password],
fun({passwd, User, Password}) ->
#passwd{us = {User, Host},
[{passwd, [us, password],
fun({passwd, {User, _Host}, Password}) ->
#passwd{user_host = {User, Host},
password = Password}
end}]),
end}]);
update_table(Host, odbc) ->
gen_storage_migration:migrate_odbc(
Host, [passwd],
[{"users", ["username", "password"],
fun(_, User, Password) ->
#passwd{us = {User, Host},
#passwd{user_host = {User, Host},
password = Password}
end}]).

View File

@ -24,6 +24,37 @@
%%%
%%%----------------------------------------------------------------------
%%% Database schema (version / storage / table)
%%%
%%% 2.1.x / mnesia / last_activity
%%% us = {Username::string(), Host::string()}
%%% timestamp = now()
%%% status = string()
%%%
%%% 2.1.x / odbc / last
%%% username = varchar250
%%% seconds = text
%%% state = text
%%%
%%% 3.0.0-prealpha / mnesia / last_activity
%%% us = {Username::binary(), Host::binary()}
%%% timestamp = now()
%%% status = binary()
%%%
%%% 3.0.0-prealpha / odbc / last
%%% Same as 2.1.x
%%%
%%% 3.0.0-alpha / mnesia / last_activity
%%% user_host = {Username::binary(), Host::binary()}
%%% timestamp = now()
%%% status = binary()
%%%
%%% 3.0.0-alpha / odbc / last_activity
%%% user = varchar150
%%% host = varchar150
%%% timestamp = bigint
%%% status = text
-module(mod_last).
-author('alexey@process-one.net').
@ -43,7 +74,7 @@
-include("ejabberd.hrl").
-include("mod_privacy.hrl").
-record(last_activity, {user_server, timestamp, status}).
-record(last_activity, {user_host, timestamp, status}).
start(Host, Opts) ->
@ -54,9 +85,9 @@ start(Host, Opts) ->
[{disc_copies, [node()]},
{odbc_host, Host},
{attributes, record_info(fields, last_activity)},
{types, [{user_server, {text, text}},
{types, [{user_host, {text, text}},
{timestamp, bigint}]}]),
update_table(Host),
update_table(Host, Backend),
gen_iq_handler:add_iq_handler(ejabberd_local, HostB, ?NS_LAST_ACTIVITY,
?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_LAST_ACTIVITY,
@ -165,7 +196,7 @@ store_last_info(User, Server, TimeStamp, Status)
US = {User, Server},
F = fun() ->
gen_storage:write(Server,
#last_activity{user_server = US,
#last_activity{user_host = US,
timestamp = TimeStamp,
status = Status})
end,
@ -203,66 +234,33 @@ remove_user(User, Server) when is_binary(User), is_binary(Server) ->
ok
end.
update_table(Host) ->
Fields = record_info(fields, last_activity),
case mnesia:table_info(last_activity, attributes) of
Fields ->
convert_to_exmpp();
_ ->
update_table(Host, mnesia) ->
gen_storage_migration:migrate_mnesia(
Host, last_activity,
[{last_activity, [us, timestamp, status],
fun(#last_activity{} = LA) ->
LA
end}]),
fun({last_activity, {U, S}, Timestamp, Status}) ->
U1 = case U of
"" -> undefined;
V -> V
end,
#last_activity{user_host = {list_to_binary(U1),
list_to_binary(S)},
timestamp = Timestamp,
status = list_to_binary(Status)}
end}]);
update_table(Host, odbc) ->
gen_storage_migration:migrate_odbc(
Host, [last_activity],
[{"last", ["username", "seconds", "state"],
fun(_, Username, STimeStamp, Status) ->
case catch list_to_integer(STimeStamp) of
TimeStamp when is_integer(TimeStamp) ->
[#last_activity{user_server = {Username, Host},
[#last_activity{user_host = {Username, Host},
timestamp = TimeStamp,
status = Status}];
_ ->
?WARNING_MSG("Omitting last_activity migration item with timestamp=~p",
?WARNING_MSG("Omitting last_activity migration item"
" with timestamp=~p",
[STimeStamp])
end
end}])
end.
convert_to_exmpp() ->
Fun = fun() ->
case mnesia:first(last_activity) of
'$end_of_table' ->
none;
Key ->
case mnesia:read({last_activity, Key}) of
[#last_activity{status = Status}] when is_binary(Status) ->
none;
[#last_activity{}] ->
mnesia:foldl(fun convert_to_exmpp2/2,
done, last_activity, write)
end
end
end,
mnesia:transaction(Fun).
convert_to_exmpp2(#last_activity{user_server = {U, S} = Key, status = Status} = LA,
Acc) ->
% Remove old entry.
mnesia:delete({last_activity, Key}),
% Convert "" to undefined in JIDs.
U1 = convert_jid_to_exmpp(U),
% Convert status.
Status1 = list_to_binary(Status),
% Prepare the new record.
New_LA = LA#last_activity{user_server = {list_to_binary(U1), list_to_binary(S)},
status = Status1},
% Write the new record.
mnesia:write(New_LA),
Acc.
convert_jid_to_exmpp("") -> undefined;
convert_jid_to_exmpp(V) -> V.
end}]).

View File

@ -24,6 +24,49 @@
%%%
%%%----------------------------------------------------------------------
%%% Database schema (version / storage / table)
%%%
%%% 2.1.x / mnesia / offline_msg
%%% us = {Username::string(), Host::string()}
%%% timestamp = now()
%%% expire = never | ???
%%% from = jid_old()
%%% to = jid_old()
%%% packet = xmlelement()
%%%
%%% 2.1.x / odbc / spool
%%% username = varchar250
%%% xml = text
%%% seq = bigint unsigned
%%%
%%% 3.0.0-prealpha / mnesia / offline_msg
%%% us = {Username::string(), Host::string()}
%%% timestamp = now()
%%% expire = never | ???
%%% from = jid()
%%% to = jid()
%%% packet = #xmlel
%%%
%%% 3.0.0-prealpha / odbc / spool
%%% Same as 2.1.x
%%%
%%% 3.0.0-alpha / mnesia / offline_msg
%%% user_host = {Username::string(), Host::string()}
%%% timestamp = integer()
%%% expire = 0 | integer()
%%% from = jid()
%%% to = jid()
%%% packet = string()
%%%
%%% 3.0.0-alpha / odbc / offline_msg
%%% user = varchar150
%%% host = varchar150
%%% timestamp = bigint
%%% expire = bigint
%%% from = varchar150
%%% to = varchar150
%%% packet = varchar150
-module(mod_offline).
-author('alexey@process-one.net').
@ -50,8 +93,9 @@
-include("web/ejabberd_http.hrl").
-include("web/ejabberd_web_admin.hrl").
%% The packet is stored serialized as a string
%% TODO: packet always handled serialized?
-record(offline_msg, {user_server, timestamp, expire, from, to, packet}).
-record(offline_msg, {user_host, timestamp, expire, from, to, packet}).
-define(PROCNAME, ejabberd_offline).
-define(OFFLINE_TABLE_LOCK_THRESHOLD, 1000).
@ -72,12 +116,12 @@ start(Host, Opts) ->
{odbc_host, Host},
{type, bag},
{attributes, record_info(fields, offline_msg)},
{types, [{user_server, {text, text}},
{types, [{user_host, {text, text}},
{timestamp, bigint},
{expire, bigint},
{from, ljid},
{to, ljid}]}]),
update_table(),
{from, jid},
{to, jid}]}]),
update_table(Host, Backend),
ejabberd_hooks:add(offline_message_hook, HostB,
?MODULE, store_packet, 50),
ejabberd_hooks:add(resend_offline_messages_hook, HostB,
@ -102,12 +146,12 @@ start(Host, Opts) ->
loop(AccessMaxOfflineMsgs) ->
receive
#offline_msg{user_server=US} = Msg ->
#offline_msg{user_host=US} = Msg ->
Msgs = receive_all(US, [Msg]),
Len = length(Msgs),
%% TODO: is lower?
{User, Host} = US,
MsgsSerialized = [M#offline_msg{packet = xml:element_to_string(P)}
MsgsSerialized = [M#offline_msg{packet = exmpp_xml:document_to_list(P)}
|| #offline_msg{packet = P} = M <- Msgs],
MaxOfflineMsgs = get_max_user_messages(AccessMaxOfflineMsgs,
User, Host),
@ -115,8 +159,7 @@ loop(AccessMaxOfflineMsgs) ->
%% Only count messages if needed:
Count =
if MaxOfflineMsgs =/= infinity ->
Len + gen_storage:count_records(Host, offline_msg,
[{'=', user_server, US}]);
Len + get_queue_length(User, Host);
true ->
0
end,
@ -152,7 +195,7 @@ get_max_user_messages(AccessRule, LUser, Host) ->
receive_all(US, Msgs) ->
receive
#offline_msg{user_server=US} = Msg ->
#offline_msg{user_host=US} = Msg ->
receive_all(US, [Msg | Msgs])
after 0 ->
Msgs
@ -208,7 +251,7 @@ store_packet(From, To, Packet) ->
TimeStamp = make_timestamp(),
Expire = find_x_expire(TimeStamp, Packet#xmlel.children),
gen_mod:get_module_proc(LServer, ?PROCNAME) !
#offline_msg{user_server = {LUser, LServer},
#offline_msg{user_host = {LUser, LServer},
timestamp = TimeStamp,
expire = Expire,
from = From,
@ -275,12 +318,12 @@ find_x_event_chatstates([_ | Els], {A, B, _}) ->
find_x_event_chatstates(Els, {A, B, true}).
find_x_expire(_, []) ->
never;
0;
find_x_expire(TimeStamp, [#xmlel{ns = ?NS_MESSAGE_EXPIRE} = El | _Els]) ->
Val = exmpp_xml:get_attribute_as_list(El, 'seconds', ""),
case catch list_to_integer(Val) of
{'EXIT', _} ->
never;
0;
Int when Int > 0 ->
{MegaSecs, Secs, MicroSecs} = TimeStamp,
S = MegaSecs * 1000000 + Secs + Int,
@ -288,7 +331,7 @@ find_x_expire(TimeStamp, [#xmlel{ns = ?NS_MESSAGE_EXPIRE} = El | _Els]) ->
Secs1 = S rem 1000000,
{MegaSecs1, Secs1, MicroSecs};
_ ->
never
0
end;
find_x_expire(TimeStamp, [_ | Els]) ->
find_x_expire(TimeStamp, Els).
@ -352,7 +395,7 @@ pop_offline_messages(Ls, User, Server)
TS = make_timestamp(),
Ls ++ lists:map(
fun(R) ->
Packet = R#offline_msg.packet,
[Packet] = exmpp_xml:parse_document(R#offline_msg.packet),
{route,
R#offline_msg.from,
R#offline_msg.to,
@ -367,13 +410,13 @@ pop_offline_messages(Ls, User, Server)
%% TODO: Delete the next three lines once XEP-0091 is Obsolete
jlib:timestamp_to_xml(
calendar:now_to_universal_time(
R#offline_msg.timestamp))]
timestamp_to_now(R#offline_msg.timestamp)))]
)}
end,
lists:filter(
fun(R) ->
case R#offline_msg.expire of
never ->
0 ->
true;
TimeStamp ->
TS < TimeStamp
@ -428,156 +471,65 @@ remove_user(User, Server) when is_binary(User), is_binary(Server) ->
ok
end.
update_table() ->
Fields = record_info(fields, offline_msg),
case mnesia:table_info(offline_msg, attributes) of
Fields ->
convert_to_exmpp();
[user, timestamp, expire, from, to, packet] ->
?INFO_MSG("Converting offline_msg table from "
"{user, timestamp, expire, from, to, packet} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_offline_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, offline_msg},
{attributes, record_info(fields, offline_msg)}]),
mnesia:transform_table(offline_msg, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_offline_tmp_table),
mnesia:foldl(
fun(#offline_msg{user_server = U, from = F, to = T, packet = P} = R, _) ->
U1 = convert_jid_to_exmpp(U),
F1 = jlib:from_old_jid(F),
T1 = jlib:from_old_jid(T),
P1 = exmpp_xml:xmlelement_to_xmlel(
P,
[?DEFAULT_NS],
?PREFIXED_NS),
New_R = R#offline_msg{
user_server = {U1, Host},
from = F1,
to = T1,
packet = P1
},
mnesia:dirty_write(
mod_offline_tmp_table,
New_R)
end, ok, offline_msg)
end,
mnesia:transaction(F1),
mnesia:clear_table(offline_msg),
F2 = fun() ->
mnesia:write_lock_table(offline_msg),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_offline_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_offline_tmp_table);
[user, timestamp, from, to, packet] ->
?INFO_MSG("Converting offline_msg table from "
"{user, timestamp, from, to, packet} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_offline_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, offline_msg},
{attributes, record_info(fields, offline_msg)}]),
mnesia:transform_table(
offline_msg,
fun({_, U, TS, F, T, P}) ->
Expire = find_x_expire(TS, P#xmlelement.children),
U1 = convert_jid_to_exmpp(U),
F1 = jlib:from_old_jid(F),
T1 = jlib:from_old_jid(T),
P1 = exmpp_xml:xmlelement_to_xmlel(
P,
[?DEFAULT_NS],
?PREFIXED_NS),
#offline_msg{user_server = U1,
timestamp = TS,
expire = Expire,
from = F1,
to = T1,
packet = P1}
end, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_offline_tmp_table),
mnesia:foldl(
fun(#offline_msg{user_server = U} = R, _) ->
mnesia:dirty_write(
mod_offline_tmp_table,
R#offline_msg{user_server = {U, Host}})
end, ok, offline_msg)
end,
mnesia:transaction(F1),
mnesia:clear_table(offline_msg),
F2 = fun() ->
mnesia:write_lock_table(offline_msg),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_offline_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_offline_tmp_table);
_ ->
?INFO_MSG("Recreating offline_msg table", []),
mnesia:transform_table(offline_msg, ignore, Fields)
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
convert_to_exmpp() ->
Fun = fun() ->
case mnesia:first(offline_msg) of
'$end_of_table' ->
none;
Key ->
case mnesia:read({offline_msg, Key}) of
[#offline_msg{packet = #xmlel{}} | _] ->
none;
[#offline_msg{packet = #xmlelement{}} | _] ->
mnesia:foldl(fun convert_to_exmpp2/2,
done, offline_msg, write)
end
end
end,
mnesia:transaction(Fun).
update_table(Host, mnesia) ->
gen_storage_migration:migrate_mnesia(
Host, offline_msg,
[{offline_msg, [us, timestamp, expire, from, to, packet],
%% The field name 'us' changes to 'user_host',
%% but its position in the erlang record is the same,
%% so we can refer to it using the new field name 'user_host'.
fun(#offline_msg{user_host = {US_U, US_S},
timestamp = {TsMegaSecs, TsSecs, _TsMicroSecs},
expire = Expire,
from = From,
to = To,
packet = Packet} = OM) ->
%% Convert "" to undefined in JIDs.
US_U1 = convert_jid_to_exmpp(US_U),
US_S1 = convert_jid_to_exmpp(US_S),
From1 = jlib:from_old_jid(From),
To1 = jlib:from_old_jid(To),
Expire1 = case Expire of
never ->
0;
{MegaSecs, Secs, _MicroSecs} ->
MegaSecs * 1000000 + Secs
end,
PacketXmlel = exmpp_xml:xmlelement_to_xmlel(
Packet, [?DEFAULT_NS], ?PREFIXED_NS),
Packet1 = exmpp_xml:document_to_list(PacketXmlel),
OM#offline_msg{user_host = {US_U1, US_S1},
timestamp = TsMegaSecs * 1000000 + TsSecs,
expire = Expire1,
from = From1,
to = To1,
packet = Packet1}
end}]);
convert_to_exmpp2(#offline_msg{
user_server = {US_U, US_S},
from = From,
to = To,
packet = Packet} = R, Acc) ->
% Remove old entry.
mnesia:delete_object(R),
% Convert "" to undefined in JIDs.
US_U1 = convert_jid_to_exmpp(US_U),
US_S1 = convert_jid_to_exmpp(US_S),
From1 = jlib:from_old_jid(From),
To1 = jlib:from_old_jid(To),
% Convert stanza.
Packet1 = exmpp_xml:xmlelement_to_xmlel(Packet,
[?DEFAULT_NS], ?PREFIXED_NS),
% Prepare the new record.
New_R = R#offline_msg{
user_server = {US_U1, US_S1},
from = From1,
to = To1,
packet = Packet1},
% Write the new record.
mnesia:write(New_R),
Acc.
update_table(Host, odbc) ->
gen_storage_migration:migrate_odbc(
Host, [offline_msg],
[{"spool", ["username", "xml", "seq"],
fun(_, Username, XmlS, _Seq) ->
[Xmlel] = Els = exmpp_xml:parse_document(XmlS, [names_as_atom]),
From = jlib:short_prepd_jid(
exmpp_jid:parse(
exmpp_stanza:get_sender(Xmlel))),
Expire = find_x_expire(0, Els),
Timestamp = find_x_timestamp(Els),
[#offline_msg{user_host = {Username, Host},
timestamp = Timestamp,
expire = Expire,
from = From,
to = {Username, Host, ""},
packet = XmlS}]
end}]).
convert_jid_to_exmpp("") -> undefined;
convert_jid_to_exmpp(V) -> V.
%% Helper functions:
make_timestamp() ->
{MegaSecs, Secs, _MicroSecs} = now(),
MegaSecs * 1000000 + Secs.
@ -587,6 +539,22 @@ timestamp_to_now(Timestamp) ->
Secs = Timestamp rem 1000000,
{MegaSecs, Secs, 0}.
find_x_timestamp([]) ->
make_timestamp();
find_x_timestamp([{xmlcdata, _} | Els]) ->
find_x_timestamp(Els);
find_x_timestamp([#xmlel{ns = ?NS_DELAY} = El | Els]) ->
Stamp = exmpp_xml:get_attribute_as_list(El, 'stamp', ""),
case jlib:datetime_string_to_timestamp(Stamp) of
undefined -> find_x_timestamp(Els);
{MegaSecs, Secs, _MicroSecs} -> MegaSecs * 1000000 + Secs
end;
find_x_timestamp([_ | Els]) ->
find_x_timestamp(Els).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% Warn senders that their messages have been discarded:
discard_warn_sender(Msgs) ->
lists:foreach(
@ -616,7 +584,7 @@ user_queue(User, Server, Query, Lang) ->
exmpp_stringprep:nodeprep(User),
exmpp_stringprep:nameprep(Server)
},
{user_server, MsgsAll, Res} = try
{US, MsgsAll, Res} = try
{
US0,
lists:keysort(#offline_msg.timestamp,
@ -631,7 +599,7 @@ user_queue(User, Server, Query, Lang) ->
FMsgs =
lists:map(
fun(#offline_msg{timestamp = TimeStamp, from = From, to = To,
packet = Packet} = Msg) ->
packet = PacketInitialString} = Msg) ->
ID = jlib:encode_base64(binary_to_list(term_to_binary(Msg))),
{{Year, Month, Day}, {Hour, Minute, Second}} =
calendar:now_to_local_time(timestamp_to_now(TimeStamp)),
@ -641,6 +609,7 @@ user_queue(User, Server, Query, Lang) ->
[Year, Month, Day, Hour, Minute, Second])),
SFrom = exmpp_jid:to_list(From),
STo = exmpp_jid:to_list(To),
[Packet] = exmpp_xmlstream:parse_element(PacketInitialString),
Packet1 = exmpp_stanza:set_jids(Packet, SFrom, STo),
FPacket = exmpp_xml:node_to_list(
exmpp_xml:indent_document(Packet1, <<" ">>),
@ -654,7 +623,7 @@ user_queue(User, Server, Query, Lang) ->
)
end, Msgs),
[?XC("h1", io_lib:format(?T("~s's Offline Messages Queue"),
[us_to_list(US0)]))] ++
[us_to_list(US)]))] ++
case Res of
ok -> [?CT("Submitted"), ?P];
nothing -> []
@ -711,8 +680,8 @@ user_queue_parse_query(US, Query) ->
us_to_list({User, Server}) ->
exmpp_jid:to_list(User, Server).
get_queue_length(User, Server) ->
length(mnesia:dirty_read({offline_msg, {User, Server}})).
get_queue_length(User, Host) ->
gen_storage:dirty_count_records(Host, offline_msg, [{'=', user_host, {User, Host}}]).
get_messages_subset(User, Host, MsgsAll) ->
Access = gen_mod:get_module_opt(Host, ?MODULE, access_max_user_messages,
@ -736,8 +705,9 @@ get_messages_subset2(Max, Length, MsgsAll) ->
MsgsFirstN ++ [IntermediateMsg] ++ MsgsLastN.
webadmin_user(Acc, User, Server, Lang) ->
US = {exmpp_stringprep:nodeprep(User), exmpp_stringprep:nameprep(Server)},
QueueLen = length(gen_storage:dirty_read(Server, {offline_msg, US})),
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
QueueLen = get_queue_length(LUser, LServer),
FQueueLen = [?AC("queue/", integer_to_list(QueueLen))],
Acc ++ [?XCT("h3", "Offline Messages:")] ++ FQueueLen ++ [?C(" "), ?INPUTT("submit", "removealloffline", "Remove All Offline Messages")].
@ -761,51 +731,3 @@ webadmin_user_parse_query(_, "removealloffline", User, Server, _Query) ->
webadmin_user_parse_query(Acc, _Action, _User, _Server, _Query) ->
Acc.
update_table(Host) ->
gen_storage_migration:migrate_mnesia(
Host, offline_msg,
[{offline_msg, [us, timestamp, expire, from, to, packet],
fun(#offline_msg{expire = never,
packet = Packet} = OM) ->
OM#offline_msg{expire = 0,
packet = xml:element_to_string(Packet)};
(#offline_msg{expire = {MegaSecs, Secs, _MicroSecs},
packet = Packet} = OM) ->
OM#offline_msg{expire = MegaSecs * 1000000 + Secs,
packet = xml:element_to_string(Packet)}
end}]),
gen_storage_migration:migrate_odbc(
Host, [offline_msg],
[{"spool", ["username", "xml", "seq"],
fun(_, Username, XmlS, _Seq) ->
{xmlelement, _, Attrs, Els} = xml_stream:parse_element(XmlS),
From = jlib:jid_tolower(
jlib:string_to_jid(
xml:get_attr_s("from", Attrs))),
Expire = find_x_expire(0, Els),
Timestamp = find_x_timestamp(Els),
[#offline_msg{user_server = {Username, Host},
timestamp = Timestamp,
expire = Expire,
from = From,
to = {Username, Host, ""},
packet = XmlS}]
end}]).
find_x_timestamp([]) ->
make_timestamp();
find_x_timestamp([{xmlcdata, _} | Els]) ->
find_x_timestamp(Els);
find_x_timestamp([El | Els]) ->
case xml:get_tag_attr_s("xmlns", El) of
?NS_DELAY ->
Stamp = xml:get_tag_attr_s("stamp", El),
case jlib:datetime_string_to_timestamp(Stamp) of
undefined -> find_x_timestamp(Els);
{MegaSecs, Secs, _MicroSecs} -> MegaSecs * 1000000 + Secs
end;
_ ->
find_x_timestamp(Els)
end.

View File

@ -24,6 +24,81 @@
%%%
%%%----------------------------------------------------------------------
%%% Database schema (version / storage / table)
%%%
%%% 2.1.x / mnesia / privacy
%%% us = {Username::string(), Host::string()}
%%% default = none | ListName::string()
%%% lists = [ {ListName::string(), [listitem()]} ]
%%%
%%% 2.1.x / odbc / privacy_default_list
%%% username = varchar250
%%% name = varchar250
%%% 2.1.x / odbc / privacy_list
%%% username = varchar250
%%% name = varchar250
%%% id = bigint-unsigned
%%% 2.1.x / odbc / privacy_list_data
%%% id = bigint
%%% t = character(1)
%%% value = text
%%% action = character(1)
%%% ord = NUMERIC
%%% match_all = boolean
%%% match_iq = boolean
%%% match_message = boolean
%%% match_presence_in = boolean
%%% match_presence_out = boolean
%%%
%%% 3.0.0-prealpha / mnesia / privacy
%%% us = {Username::string(), Host::string()}
%%% default = none | ListName::string()
%%% lists = [ {ListName::string(), [listitem()]} ]
%%%
%%% 3.0.0-prealpha / odbc / privacy
%%% Same as 2.1.x
%%%
%%% 3.0.0-alpha / mnesia / privacy_default_list
%%% user_host = {Username::string(), Server::string()}
%%% name = string()
%%% 3.0.0-alpha / mnesia / privacy_list
%%% user_host = {Username::string(), Server::string()}
%%% name = string()
%%% 3.0.0-alpha / mnesia / privacy_list_data
%%% user_host = {Username::string(), Server::string()}
%%% name = string()
%%% type = jid | group | subscription | none
%%% value = JID::binary() | Group::binary() | <<"none">> | <<"both">> | <<"from">> | <<"to">>
%%% action = allow | deny
%%% order = integer()
%%% match_all = boolean()
%%% match_iq = boolean()
%%% match_message = boolean()
%%% match_presence_in = boolean()
%%% match_presence_out = boolean()
%%%
%%% 3.0.0-alpha / odbc / privacy_default_list
%%% user = varchar150
%%% host = varchar150
%%% name = text
%%% 3.0.0-alpha / odbc / privacy_list
%%% user = varchar150
%%% host = varchar150
%%% name = varchar150
%%% 3.0.0-alpha / odbc / privacy_list_data
%%% user = varchar150
%%% host = varchar150
%%% name = varchar150
%%% type = varchar150
%%% value = varchar150
%%% action = varchar150
%%% order = int 11
%%% match_all = varchar150
%%% match_iq = varchar150
%%% match_message = varchar150
%%% match_presence_in = varchar150
%%% match_presence_out = varchar150
-module(mod_privacy).
-author('alexey@process-one.net').
@ -43,9 +118,9 @@
-include("ejabberd.hrl").
-include("mod_privacy.hrl").
-record(privacy_list, {username_server, name}).
-record(privacy_default_list, {username_server, name}).
-record(privacy_list_data, {username_server, name,
-record(privacy_list, {user_host, name}).
-record(privacy_default_list, {user_host, name}).
-record(privacy_list_data, {user_host, name,
type, value, action, order,
match_all, match_iq, match_message,
match_presence_in, match_presence_out}).
@ -59,21 +134,21 @@ start(Host, Opts) ->
{odbc_host, Host},
{type, bag},
{attributes, record_info(fields, privacy_list)},
{types, [{username_server, {text, text}}]}]),
{types, [{user_host, {text, text}}]}]),
gen_storage:create_table(Backend, HostB, privacy_default_list,
[{disc_copies, [node()]},
{odbc_host, Host},
{attributes, record_info(fields, privacy_default_list)},
{types, [{username_server, {text, text}}]}]),
{types, [{user_host, {text, text}}]}]),
gen_storage:create_table(Backend, HostB, privacy_list_data,
[{disc_copies, [node()]},
{odbc_host, Host},
{type, bag},
{attributes, record_info(fields, privacy_list_data)},
{types, [{username_server, {text, text}},
{action, atom},
{types, [{user_host, {text, text}},
{type, atom},
{value, atom},
{value, binary},
{action, atom},
{order, int},
{match_all, atom},
{match_iq, atom},
@ -81,7 +156,7 @@ start(Host, Opts) ->
{match_presence_in, atom},
{match_presence_out, atom}
]}]),
update_tables(Host),
update_tables(Host, Backend),
gen_storage:add_table_index(Host, privacy_list, name),
gen_storage:add_table_index(Host, privacy_list_data, name),
ejabberd_hooks:add(privacy_iq_get, HostB,
@ -160,10 +235,7 @@ process_lists_get(LUser, LServer, Active) ->
[] ->
{result, #xmlel{ns = ?NS_PRIVACY, name = 'query'}};
_ ->
LItems = lists:map(
fun({N, _}) ->
exmpp_xml:set_attribute(#xmlel{ns = ?NS_PRIVACY, name = list}, name, N)
end, Lists),
LItems = [exmpp_xml:set_attribute(#xmlel{ns = ?NS_PRIVACY, name = list}, name, N) || N <- Lists],
DItems =
case Default of
none ->
@ -188,13 +260,13 @@ process_list_get(_LUser, _LServer, false) ->
process_list_get(LUser, LServer, Name) ->
F = fun() ->
case gen_storage:select(LServer, privacy_list,
[{'=', username_server, {LUser, LServer}},
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]) of
[] ->
none;
[#privacy_list{}] ->
gen_storage:select(LServer, privacy_list_data,
[{'=', username_server, {LUser, LServer}},
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}])
end
end,
@ -217,8 +289,8 @@ item_to_xml(Item) ->
none ->
Attrs1;
Type ->
[?XMLATTR('type', type_to_binary(Item#privacy_list_data.type)),
?XMLATTR('value', value_to_binary(Type, Item#privacy_list_data.value)) |
[?XMLATTR('type', type_to_binary(Type)),
?XMLATTR('value', Item#privacy_list_data.value) |
Attrs1]
end,
SubEls = case Item#privacy_list_data.match_all of
@ -270,21 +342,6 @@ type_to_binary(Type) ->
subscription -> <<"subscription">>
end.
value_to_binary(Type, Val) ->
case Type of
jid ->
{N, D, R} = Val,
exmpp_jid:to_binary(N, D, R);
group -> Val;
subscription ->
case Val of
both -> <<"both">>;
to -> <<"to">>;
from -> <<"from">>;
none -> <<"none">>
end
end.
list_to_action(S) ->
@ -331,13 +388,13 @@ process_default_set(LUser, LServer, false) ->
process_default_set(LUser, LServer, Name) ->
F = fun() ->
case gen_storage:select(LServer, privacy_list,
[{'=', username_server, {LUser, LServer}},
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]) of
[] ->
{error, 'item-not-found'};
[#privacy_list{}] ->
gen_storage:write(LServer,
#privacy_default_list{username_server = {LUser, LServer},
#privacy_default_list{user_host = {LUser, LServer},
name = Name}),
{result, []}
end
@ -358,11 +415,11 @@ process_active_set(_LUser, _LServer, false) ->
process_active_set(LUser, LServer, Name) ->
F = fun() ->
case gen_storage:select(LServer, privacy_list,
[{'=', username_server, {LUser, LServer}},
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]) of
[#privacy_list{}] ->
List = gen_storage:select(LServer, privacy_list_data,
[{'=', username_server, {LUser, LServer}},
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]),
{result, [], #userlist{name = Name,
list = list_data_to_items(List)}};
@ -394,10 +451,10 @@ process_list_set(LUser, LServer, Name, Els) ->
{error, 'conflict'};
_ ->
gen_storage:delete_where(LServer, privacy_list,
[{'=', username_server, {LUser, LServer}},
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]),
gen_storage:delete_where(LServer, privacy_list_data,
[{'=', username_server, {LUser, LServer}},
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]),
{result, []}
end
@ -421,14 +478,14 @@ process_list_set(LUser, LServer, Name, Els) ->
F = fun() ->
OldData =
gen_storage:select(LServer, privacy_list_data,
[{'=', username_server, {LUser, LServer}},
[{'=', user_host, {LUser, LServer}},
{'=', name, Name}]),
lists:foreach(
fun(Data1) ->
gen_storage:delete_object(LServer, Data1)
end, OldData),
gen_storage:write(LServer, #privacy_list{username_server = {LUser, LServer},
gen_storage:write(LServer, #privacy_list{user_host = {LUser, LServer},
name = Name}),
NewData = list_items_to_data(LUser, LServer, Name, List),
lists:foreach(
@ -467,7 +524,7 @@ parse_items([], Res) ->
lists:reverse(Res);
parse_items([El = #xmlel{name = item} | Els], Res) ->
Type = exmpp_xml:get_attribute_as_list(El, type, false),
Value = exmpp_xml:get_attribute_as_list(El, value, false),
Value = exmpp_xml:get_attribute_as_binary(El, value, false),
SAction =exmpp_xml:get_attribute_as_list(El, action, false),
SOrder = exmpp_xml:get_attribute_as_list(El, order, false),
Action = case catch list_to_action(SAction) of
@ -489,38 +546,17 @@ parse_items([El = #xmlel{name = item} | Els], Res) ->
(Action /= false) and (Order /= false) ->
I1 = #listitem{action = Action, order = Order},
I2 = case {Type, Value} of
{T, V} when is_list(T), is_list(V) ->
{T, V} when is_list(T), is_binary(V) ->
case T of
"jid" ->
try
JID = exmpp_jid:parse(V),
I1#listitem{
type = jid,
value = jlib:short_prepd_jid(JID)}
catch
_ ->
false
end;
I1#listitem{type = jid,
value = V};
"group" ->
I1#listitem{type = group,
value = V};
"subscription" ->
case V of
"none" ->
I1#listitem{type = subscription,
value = none};
"both" ->
I1#listitem{type = subscription,
value = both};
"from" ->
I1#listitem{type = subscription,
value = from};
"to" ->
I1#listitem{type = subscription,
value = to};
_ ->
false
end
I1#listitem{type = subscription,
value = V}
end;
{T, false} when is_list(T) ->
false;
@ -566,10 +602,6 @@ parse_matches1(_Item, [#xmlel{} | _Els]) ->
%% storage representation to ejabberd representation
list_data_to_items(Data) ->
List =
@ -592,7 +624,7 @@ list_data_to_items(Data) ->
list_items_to_data(LUser, LServer, Name, List) ->
lists:map(
fun(Item) ->
#privacy_list_data{username_server = {LUser, LServer},
#privacy_list_data{user_host = {LUser, LServer},
name = Name,
type = Item#listitem.type,
value = Item#listitem.value,
@ -626,7 +658,7 @@ get_user_list(_, User, Server)
#userlist{};
[#privacy_default_list{name = Default}] ->
Data = gen_storage:select(LServer, privacy_list_data,
[{'=', username_server, {LUser, LServer}},
[{'=', user_host, {LUser, LServer}},
{'=', name, Default}]),
List = list_data_to_items(Data),
NeedDb = is_list_needdb(List),
@ -734,18 +766,15 @@ is_ptype_match(Item, PType) ->
%% TODO: Investigate this: sometimes Value has binaries, other times has strings
is_type_match(Type, Value, JID, Subscription, Groups) ->
case Type of
jid ->
{User, Server, Resource} = Value,
((User == undefined) orelse (User == []) orelse (User == exmpp_jid:prep_node(JID)))
andalso ((Server == undefined) orelse (Server == []) orelse (Server == exmpp_jid:prep_domain(JID)))
andalso ((Resource == undefined) orelse (Resource == []) orelse (Resource == exmpp_jid:prep_resource(JID)));
subscription ->
Value == Subscription;
group ->
lists:member(Value, Groups)
end.
is_type_match(jid, Value, JID, _Subscription, _Groups) ->
{User, Server, Resource} = exmpp_jid:to_lower(exmpp_jid:parse(Value)),
((User == undefined) orelse (User == []) orelse (User == exmpp_jid:prep_node(JID)))
andalso ((Server == undefined) orelse (Server == []) orelse (Server == exmpp_jid:prep_domain(JID)))
andalso ((Resource == undefined) orelse (Resource == []) orelse (Resource == exmpp_jid:prep_resource(JID)));
is_type_match(subscription, Value, _JID, Subscription, _Groups) ->
Value == Subscription;
is_type_match(group, Value, _JID, _Subscription, Groups) ->
lists:member(Value, Groups).
remove_user(User, Server) ->
@ -772,14 +801,9 @@ updated_list(_,
Old
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
update_tables(Host) ->
Fields = record_info(fields, privacy_list),
case mnesia:table_info(privacy_list, attributes) of
Fields ->
convert_to_exmpp();
_ -> ok
end,
update_tables(Host, mnesia) ->
gen_storage_migration:migrate_mnesia(
Host, privacy_default_list,
[{privacy, [us, default, lists],
@ -796,11 +820,12 @@ update_tables(Host) ->
match_message = MatchMessage,
match_presence_in = MatchPresenceIn,
match_presence_out = MatchPresenceOut}) ->
ValueBin = convert_value_to_binary(Value),
gen_storage:write(Host,
#privacy_list_data{username_server = US,
#privacy_list_data{user_host = US,
name = Name,
type = Type,
value = Value,
value = ValueBin,
action = Action,
order = Order,
match_all = MatchAll,
@ -810,23 +835,26 @@ update_tables(Host) ->
match_presence_out = MatchPresenceOut})
end, List),
gen_storage:write(Host,
#privacy_list{username_server = US,
#privacy_list{user_host = US,
name = Name})
end, Lists),
if
is_list(Default) ->
#privacy_default_list{username_server = US,
#privacy_default_list{user_host = US,
name = Default};
true -> null
end
end}]),
end}]);
update_tables(Host, odbc) ->
gen_storage_migration:migrate_odbc(
Host, [privacy_default_list, privacy_list, privacy_list_data],
[{[{"privacy_list", ["username", "name", "id"]},
{"privacy_list_data", []}],
{"privacy_list_data", ["id","t","value","action","ord","match_all","match_iq","match_message",
"match_presence_in","match_presence_out"]}],
fun(SELECT, Username, Name, Id) ->
US = {Username, Host},
DefaultLists = [#privacy_default_list{username_server = US,
DefaultLists = [#privacy_default_list{user_host = US,
name = Name}
|| [_, _] <- SELECT(["username", "name"],
"privacy_default_list",
@ -859,22 +887,22 @@ update_tables(Host) ->
{subscription, to}
end
end,
ValueBin = convert_value_to_binary(Value),
Action =
case SAction of
"a" -> allow;
"d" -> deny
end,
Order = list_to_integer(SOrder),
MatchAll = SMatchAll == "1" orelse SMatchAll == "t",
MatchIQ = SMatchIQ == "1" orelse SMatchIQ == "t" ,
MatchMessage = SMatchMessage == "1" orelse SMatchMessage == "t",
MatchPresenceIn = SMatchPresenceIn == "1" orelse SMatchPresenceIn == "t",
MatchPresenceOut = SMatchPresenceOut == "1" orelse SMatchPresenceOut == "t",
#privacy_list_data{username_server = US,
MatchAll = ejabberd_odbc:to_bool(SMatchAll),
MatchIQ = ejabberd_odbc:to_bool(SMatchIQ),
MatchMessage = ejabberd_odbc:to_bool(SMatchMessage),
MatchPresenceIn = ejabberd_odbc:to_bool(SMatchPresenceIn),
MatchPresenceOut = ejabberd_odbc:to_bool(SMatchPresenceOut),
#privacy_list_data{user_host = US,
name = Name,
type = Type,
value = Value,
value = ValueBin,
action = Action,
order = Order,
match_all = MatchAll,
@ -887,53 +915,26 @@ update_tables(Host) ->
"match_presence_out"],
"privacy_list_data",
[{"id", Id}])),
[#privacy_list{username_server = US,
[#privacy_list{user_host = US,
name = Name} | DefaultLists ++ ListData]
end},
end},
{"privacy_default_list", ["username", "name"],
fun(_, Username, Name) ->
US = {Username, Host},
[#privacy_default_list{username_server = US,
name = Name}]
fun(_, Username, Name) ->
US = {Username, Host},
[#privacy_default_list{user_host = US,
name = Name}]
end}
]).
convert_to_exmpp() ->
Fun = fun() ->
mnesia:foldl(fun convert_to_exmpp2/2, done, privacy, write)
end,
mnesia:transaction(Fun).
convert_to_exmpp2(#privacy{us = {U, S} = Key, lists = L} = P, Acc) ->
U1 = convert_jid_to_exmpp(U),
L1 = convert_lists_to_exmpp(L),
New_P = P#privacy{
us = {U1, S},
lists = L1
},
if
New_P /= P -> mnesia:delete({privacy, Key}), mnesia:write(New_P);
true -> ok
end,
Acc.
convert_jid_to_exmpp("") -> undefined;
convert_jid_to_exmpp(V) -> V.
convert_lists_to_exmpp(L) ->
convert_lists_to_exmpp2(L, []).
convert_lists_to_exmpp2([{Name, List} | Rest], Result) ->
convert_lists_to_exmpp2(Rest,
[{Name, convert_list_to_exmpp(List, [])} | Result]);
convert_lists_to_exmpp2([], Result) ->
lists:reverse(Result).
convert_list_to_exmpp([#listitem{type = jid, value = {U, S, R}} = I | Rest],
Result) ->
U1 = convert_jid_to_exmpp(U),
R1 = convert_jid_to_exmpp(R),
New_I = I#listitem{value = {U1, S, R1}},
convert_list_to_exmpp(Rest, [New_I | Result]);
convert_list_to_exmpp([], Result) ->
lists:reverse(Result).
convert_value_to_binary({U, H, R}) ->
exmpp_jid:to_binary(U, H, R);
convert_value_to_binary(Value) when is_list(Value) ->
list_to_binary(Value);
convert_value_to_binary(none) ->
<<"none">>;
convert_value_to_binary(both) ->
<<"both">>;
convert_value_to_binary(from) ->
<<"from">>;
convert_value_to_binary(to) ->
<<"to">>.

View File

@ -19,7 +19,7 @@
%%%
%%%----------------------------------------------------------------------
-record(privacy, {us,
-record(privacy, {user_host,
default = none,
lists = []}).

View File

@ -24,6 +24,34 @@
%%%
%%%----------------------------------------------------------------------
%%% Database schema (version / storage / table)
%%%
%%% 2.1.x / mnesia / private_storage
%%% usns = {Username::string(), Host::string(), Namespace::string()}
%%% xml = xmlelement()
%%%
%%% 2.1.x / odbc / private_storage
%%% username = varchar250
%%% namespace = varchar250
%%% data = text
%%%
%%% 3.0.0-prealpha / mnesia / private_storage
%%% usns = {Username::binary(), Host::binary(), Namespace::atom()}
%%% xml = xmlel()
%%%
%%% 3.0.0-prealpha / odbc / private_storage
%%% Same as 2.1.x
%%%
%%% 3.0.0-alpha / mnesia / private_storage
%%% user_host_ns = {Username::binary(), Host::binary(), Namespace::atom()}
%%% xml = xmlel()
%%%
%%% 3.0.0-alpha / odbc / private_storage
%%% user = varchar
%%% host = varchar
%%% ns = varchar250
%%% xml = text
-module(mod_private).
-author('alexey@process-one.net').
@ -38,8 +66,8 @@
-include("ejabberd.hrl").
%% TODO: usns instead of user_server_ns requires no migration
-record(private_storage, {user_server_ns, xml}).
%% TODO: usns instead of user_host_ns requires no migration
-record(private_storage, {user_host_ns, xml}).
start(Host, Opts) ->
HostB = list_to_binary(Host),
@ -49,8 +77,8 @@ start(Host, Opts) ->
[{disc_only_copies, [node()]},
{odbc_host, Host},
{attributes, record_info(fields, private_storage)},
{types, [{user_server_ns, {text, text, text}}]}]),
update_table(Host),
{types, [{user_host_ns, {binary, binary, atom}}, {xml, xmlel}]}]),
update_table(Host, Backend),
ejabberd_hooks:add(remove_user, HostB,
?MODULE, remove_user, 50),
gen_iq_handler:add_iq_handler(ejabberd_sm, HostB, ?NS_PRIVATE,
@ -146,10 +174,11 @@ check_ns(_From, _To, #iq{payload = SubEl}) ->
end
end.
%% The xml is stored as xmlel() in mnesia, but as text in odbc
set_data(LUser, LServer, El) ->
XMLNS = exmpp_xml:get_ns_as_atom(El),
gen_storage:write(LServer,
#private_storage{user_server_ns = {LUser, LServer, XMLNS},
#private_storage{user_host_ns = {LUser, LServer, XMLNS},
xml = El}).
get_data(LUser, LServer, Els) ->
@ -175,9 +204,9 @@ remove_user(User, Server)
LServer = exmpp_stringprep:nameprep(Server),
F = fun() ->
Records = gen_storage:select(LServer, private_storage,
[{'=', user_server_ns, {LUser, LServer, '_'}}]),
[{'=', user_host_ns, {LUser, LServer, '_'}}]),
lists:foreach(
fun(#private_storage{user_server_ns = USNS}) ->
fun(#private_storage{user_host_ns = USNS}) ->
gen_storage:delete(LServer, {private_storage, USNS})
end, Records)
end,
@ -187,97 +216,27 @@ remove_user(User, Server)
ok
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
update_table(Host) ->
Fields = record_info(fields, private_storage),
case mnesia:table_info(private_storage, attributes) of
Fields ->
convert_to_exmpp(),
gen_storage_migration:migrate_mnesia(
Host, private_storage,
[{private_storage, [userns, xml],
fun({private_storage, {User, NS}, Xml}) ->
#private_storage{user_server_ns = {User, Host, NS},
xml = lists:flatten(xml:element_to_string(Xml))}
end},
{private_storage, [user_server_ns, xml],
fun({private_storage, {User, Server, NS}, Xml}) ->
#private_storage{user_server_ns = {User, Server, NS},
xml = lists:flatten(xml:element_to_string(Xml))}
end}]),
gen_storage_migration:migrate_odbc(
Host, [private_storage],
[{"private_storage", ["username", "namespace", "data"],
fun(_, Username, Namespace, Data) ->
[#private_storage{user_server_ns = {Username, Host, Namespace},
xml = Data}]
end}]);
[userns, xml] ->
?INFO_MSG("Converting private_storage table from "
"{user, default, lists} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_private_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, private_storage},
{attributes, record_info(fields, private_storage)}]),
mnesia:transform_table(private_storage, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_private_tmp_table),
mnesia:foldl(
fun(#private_storage{user_server_ns = {U, NS}, xml = El} = R, _) ->
NS1 = list_to_atom(NS),
El0 = exmpp_xml:xmlelement_to_xmlel(El,
[?NS_PRIVATE], [{?NS_XMPP, ?NS_XMPP_pfx}]),
El1 = exmpp_xml:remove_whitespaces_deeply(El0),
mnesia:dirty_write(
mod_private_tmp_table,
R#private_storage{user_server_ns = {U, Host, NS1}, xml = El1})
end, ok, private_storage)
end,
mnesia:transaction(F1),
mnesia:clear_table(private_storage),
F2 = fun() ->
mnesia:write_lock_table(private_storage),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_private_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_private_tmp_table);
_ ->
?INFO_MSG("Recreating private_storage table", []),
mnesia:transform_table(private_storage, ignore, Fields)
end.
update_table(Host, mnesia) ->
gen_storage_migration:migrate_mnesia(
Host, private_storage,
[{private_storage, [usns, xml],
fun({private_storage, {User, Server, NS}, Xml}) ->
U1 = list_to_binary(User),
S1 = list_to_binary(Server),
NS1 = list_to_atom(NS),
El1 = exmpp_xml:xmlelement_to_xmlel(Xml, [?NS_PRIVATE],
[{?NS_XMPP, ?NS_XMPP_pfx}]),
#private_storage{user_host_ns = {U1, S1, NS1},
xml = El1}
end}]);
convert_to_exmpp() ->
Fun = fun() ->
case mnesia:first(private_storage) of
'$end_of_table' ->
none;
{U, _S, _NS} when is_binary(U) ->
none;
{U, _S, _NS} when is_list(U) ->
mnesia:foldl(fun convert_to_exmpp2/2,
done, private_storage, write)
end
end,
mnesia:transaction(Fun).
convert_to_exmpp2(#private_storage{user_server_ns = {U, S, NS} = Key, xml = El} = R,
Acc) ->
mnesia:delete({private_storage, Key}),
U1 = list_to_binary(U),
S1 = list_to_binary(S),
NS1 = list_to_atom(NS),
El1 = exmpp_xml:xmlelement_to_xmlel(El,
[?NS_PRIVATE], [{?NS_XMPP, ?NS_XMPP_pfx}]),
New_R = R#private_storage{
user_server_ns = {U1, S1, NS1},
xml = El1},
mnesia:write(New_R),
Acc.
update_table(Host, odbc) ->
gen_storage_migration:migrate_odbc(
Host, [private_storage],
[{"private_storage", ["username", "namespace", "data"],
fun(_, Username, Namespace, Data) ->
[#private_storage{user_host_ns = {Username, Host, Namespace},
xml = Data}]
end}]).

View File

@ -33,6 +33,73 @@
%%% Roster version is a hash digest of the entire roster.
%%% No additional data is stored in DB.
%%% Database schema (version / storage / table)
%%%
%%% 2.1.x / mnesia / roster
%%% usj = {Username::string(), Host::string(), {ContactUsername::string(), ContactServer::string(), ""}}
%%% us = {Username::string(), Host::string()}
%%% jid = {ContactUsername::string(), ContactServer::string(), ""}
%%% name = ContactRosterName::string()
%%% subscription = none | from | to | both
%%% ask = none | out | in
%%% groups = [GroupName::string()]
%%% askmessage = binary()
%%% xs = [xmlelement()]
%%%
%%% 2.1.x / odbc / rosterusers
%%% username = varchar250
%%% jid = varchar250
%%% nick = text
%%% subscription = character1
%%% ask = character1
%%% askmessage = text
%%% server = character1
%%% subscribe = text
%%% type = text
%%% 2.1.x / odbc / rostergroups
%%% username = varchar250
%%% jid = varchar250
%%% grp = text
%%%
%%% 3.0.0-prealpha / mnesia / roster
%%% usj = {Username::binary(), Host::binary(), {ContactUsername::binary(), ContactServer::binary(), undefined}}
%%% us = {Userma,e::binary(), Host::binary()}
%%% jid = {ContactUsername::binary(), ContactServer::binary(), undefined}
%%% name = ContactRosterName::binary()
%%% subscription = none | from | to | both
%%% ask = none | out | in
%%% groups = [GroupName::binary()]
%%% askmessage = binary()
%%% xs = [xmlel()]
%%%
%%% 3.0.0-prealpha / odbc / rosterusers
%%% 3.0.0-prealpha / odbc / rostergroups
%%% Same as 2.1.x
%%%
%%% 3.0.0-alpha / mnesia / rosteritem
%%% user_host_jid = {Username::binary(), Host::binary(), {ContactUsername::binary(), ContactServer::binary(), undefined}}
%%% name = ContactRosterName::binary()
%%% subscription = none | from | to | both
%%% ask = none | out | in
%%% askmessage = binary()
%%% 3.0.0-alpha / mnesia / rostergroup
%%% user_host_jid = {Username::binary(), Host::binary(), {ContactUsername::binary(), ContactServer::binary(), undefined}}
%%% grp = GroupName::binary()
%%%
%%% 3.0.0-alpha / odbc / rosteritem
%%% user = varchar250
%%% host = varchar250
%%% jid = varchar250
%%% name = text
%%% subscription = text = none | from | to | both
%%% ask = text = none | out | in
%%% askmessage = text
%%% 3.0.0-alpha / odbc / rostergroup
%%% user = varchar250
%%% host = varchar250
%%% jid = varchar250
%%% grp = varchar250
-module(mod_roster).
-author('alexey@process-one.net').
@ -62,29 +129,6 @@
-include("web/ejabberd_http.hrl").
-include("web/ejabberd_web_admin.hrl").
%% @type rosteritem() = {roster, USJ, US, Contact_JID, Name, Subscription, Ask, Groups, Askmessage, Xs}
%% USJ = {LUser, LServer, Prepd_Contact_JID}
%% LUser = binary()
%% LServer = binary()
%% Prepd_Contact_JID = jlib:shortjid()
%% US = {LUser, LServer}
%% Contact_JID = jlib:shortjid()
%% Name = binary()
%% Subscription = none | to | from | both
%% Ask = none | out | in | both
%% Groups = [binary()]
%% Askmessage = binary()
%% Xs = [exmpp_xml:xmlel()]
%% TODO: keep a non-prepped jid like in mnesia mod_roster?
-record(rosteritem, {user_server_jid,
name = "",
subscription = none,
ask = none,
askmessage = ""}).
-record(rostergroup, {user_server_jid,
grp}).
%% @spec (Host, Opts) -> term()
%% Host = string()
%% Opts = list()
@ -98,7 +142,7 @@ start(Host, Opts) when is_list(Host) ->
rosteritem, [{disc_copies, [node()]},
{odbc_host, Host},
{attributes, record_info(fields, rosteritem)},
{types, [{user_server_jid, {text, text, ljid}},
{types, [{user_host_jid, {text, text, ljid}},
{subscription, atom},
{ask, atom}]}]),
gen_storage:create_table(Backend, HostB,
@ -106,10 +150,10 @@ start(Host, Opts) when is_list(Host) ->
{odbc_host, Host},
{type, bag},
{attributes, record_info(fields, rostergroup)},
{types, [{user_server_jid, {text, text, ljid}}]}]),
{types, [{user_host_jid, {text, text, ljid}}]}]),
mnesia:create_table(roster_version, [{disc_copies, [node()]},
{attributes, record_info(fields, roster_version)}]),
update_tables(Host),
update_table(Host, Backend),
mnesia:add_table_index(roster, us),
mnesia:add_table_index(roster_version, us),
ejabberd_hooks:add(roster_get, HostB,
@ -315,19 +359,20 @@ get_user_roster(Acc, {U, S}) when is_binary(U), is_binary(S) ->
true
end, Items) ++ Acc.
%% Reads the roster information from the database, and returns a list of #roster records.
get_roster(LUser, LServer) ->
F = fun() ->
U = gen_storage:select(LServer, rosteritem,
[{'=', user_server_jid, {LUser, LServer, '_'}}]),
[{'=', user_host_jid, {LUser, LServer, '_'}}]),
G = gen_storage:select(LServer, rostergroup,
[{'=', user_server_jid, {LUser, LServer, '_'}}]),
[{'=', user_host_jid, {LUser, LServer, '_'}}]),
[storageroster_to_roster(Rosteritem, G) ||
Rosteritem <- U]
end,
{atomic, Rs} = gen_storage:transaction(LServer, rosteritem, F),
Rs.
storageroster_to_roster(#rosteritem{user_server_jid = {U, S, JID} = USJ,
storageroster_to_roster(#rosteritem{user_host_jid = {U, S, JID} = USJ,
name = Name,
subscription = Subscription,
ask = Ask,
@ -336,21 +381,26 @@ storageroster_to_roster(#rosteritem{user_server_jid = {U, S, JID} = USJ,
US = {U, S},
Groups =
lists:foldl(
fun(#rostergroup{user_server_jid = USJ1, grp = G}, R)
fun(#rostergroup{user_host_jid = USJ1, grp = G}, R)
when USJ =:= USJ1 ->
[G | R];
%% G is a string when using odbc beckend, and a binary when using mnesia
GString = convert_to_string(G),
[GString | R];
(_, R) ->
R
end, [], Rostergroups),
#roster{usj = {US, JID},
us = US,
jid = JID,
name = Name,
name = convert_to_string(Name),
subscription = Subscription,
ask = Ask,
askmessage = AskMessage,
groups = Groups}.
convert_to_string(A) when is_binary(A) -> binary_to_list(A);
convert_to_string(A) when is_list(A) -> A.
%% @spec (Item) -> XML
%% Item = rosteritem()
%% XML = exmpp_xml:xmlel()
@ -441,7 +491,7 @@ process_item_set(From, To, #xmlel{} = El) ->
ask = Ask2,
askmessage = AskMessage2,
groups = Groups} ->
I2 = #rosteritem{user_server_jid = {LUser, LServer, LJID},
I2 = #rosteritem{user_host_jid = {LUser, LServer, LJID},
name = Name,
subscription = Subscription2,
ask = Ask2,
@ -451,7 +501,7 @@ process_item_set(From, To, #xmlel{} = El) ->
lists:foreach(
fun(Group) ->
gen_storage:write(LServer,
#rostergroup{user_server_jid = {LUser, LServer, LJID},
#rostergroup{user_host_jid = {LUser, LServer, LJID},
grp = Group})
end, Groups)
end,
@ -635,7 +685,7 @@ get_subscription_lists(_, User, Server)
try
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
case gen_storage:dirty_select(LServer, rosteritem, [{'=', user_server_jid, {LUser, LServer, '_'}}]) of
case gen_storage:dirty_select(LServer, rosteritem, [{'=', user_host_jid, {LUser, LServer, '_'}}]) of
Items when is_list(Items) ->
fill_subscription_lists(Items, [], []);
_ ->
@ -653,7 +703,7 @@ get_subscription_lists(_, User, Server)
%% New_F = [jlib:shortjid()]
%% New_T = [jlib:shortjid()]
fill_subscription_lists([#rosteritem{user_server_jid = {_, _, LJ},
fill_subscription_lists([#rosteritem{user_host_jid = {_, _, LJ},
subscription = Subscription} | Is],
F, T) ->
case Subscription of
@ -714,7 +764,7 @@ process_subscription(Direction, User, Server, JID1, Type, Reason)
F = fun() ->
Item = case gen_storage:read(LServer, {rosteritem, {LUser, LServer, LJID}}) of
[] ->
#rosteritem{user_server_jid = {LUser, LServer, LJID}
#rosteritem{user_host_jid = {LUser, LServer, LJID}
};
[I] ->
I
@ -911,12 +961,12 @@ remove_user(User, Server)
fun(R) ->
gen_storage:delete_object(LServer, R)
end,
gen_storage:select(LServer, rosteritem, [{'=', user_server_jid, {LUser, LServer, '_'}}])),
gen_storage:select(LServer, rosteritem, [{'=', user_host_jid, {LUser, LServer, '_'}}])),
lists:foreach(
fun(R) ->
gen_storage:delete_object(LServer, R)
end,
gen_storage:select(LServer, rostergroup, [{'=', user_server_jid, {LUser, LServer, '_'}}]))
gen_storage:select(LServer, rostergroup, [{'=', user_host_jid, {LUser, LServer, '_'}}]))
end,
gen_storage:transaction(LServer, rosteritem, F)
catch
@ -1014,7 +1064,7 @@ process_item_set_t(LUser, LServer, #xmlel{} = El) ->
ask = Ask,
askmessage = AskMessage,
groups = Groups} ->
gen_storage:write(LServer, #rosteritem{user_server_jid = {LUser, LServer, LJID},
gen_storage:write(LServer, #rosteritem{user_host_jid = {LUser, LServer, LJID},
name = Name,
subscription = Subscription,
ask = Ask,
@ -1023,7 +1073,7 @@ process_item_set_t(LUser, LServer, #xmlel{} = El) ->
lists:foreach(
fun(Group) ->
gen_storage:write(LServer,
#rostergroup{user_server_jid = {LUser, LServer, LJID},
#rostergroup{user_host_jid = {LUser, LServer, LJID},
grp = Group})
end, Groups)
end
@ -1082,10 +1132,10 @@ get_in_pending_subscriptions(Ls, User, Server)
JID = exmpp_jid:make(User, Server),
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
case gen_storage:dirty_select(LServer, rosteritem, [{'=', user_server_jid, {LUser, LServer, '_'}}]) of
case gen_storage:dirty_select(LServer, rosteritem, [{'=', user_host_jid, {LUser, LServer, '_'}}]) of
Result when is_list(Result) ->
Ls ++ lists:map(
fun(#rosteritem{user_server_jid = {_, _, RJID},
fun(#rosteritem{user_host_jid = {_, _, RJID},
askmessage = Message}) ->
Status = if is_binary(Message) ->
binary_to_list(Message);
@ -1165,169 +1215,92 @@ get_jid_info(_, User, Server, JID)
{none, []}
end.
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% @hidden
%% Only supports migration from ejabberd 1.1.2 or higher.
update_table() ->
Fields = record_info(fields, roster),
case mnesia:table_info(roster, attributes) of
Fields ->
convert_to_exmpp();
[uj, user, jid, name, subscription, ask, groups, xattrs, xs] ->
convert_table1(Fields);
[usj, us, jid, name, subscription, ask, groups, xattrs, xs] ->
convert_table2(Fields);
_ ->
?INFO_MSG("Recreating roster table", []),
mnesia:transform_table(roster, ignore, Fields)
end.
update_table(Host, mnesia) ->
gen_storage_migration:migrate_mnesia(
Host, rosteritem,
[{roster, [usj, us, jid, name, subscription, ask, groups, askmessage, xs],
fun({roster, USJ, _, _, Name, Subscription, Ask, Groups, AskMessage, _Xs}) ->
%% Convert "" to undefined in JIDs and string() to binary().
{USJ_U, USJ_S, {USJ_JU, USJ_JS, USJ_JR}} = USJ,
USJ_U1 = convert_jid_to_exmpp(USJ_U),
USJ_S1 = convert_jid_to_exmpp(USJ_S),
USJ_JU1 = convert_jid_to_exmpp(USJ_JU),
USJ_JS1 = convert_jid_to_exmpp(USJ_JS),
USJ_JR1 = convert_jid_to_exmpp(USJ_JR),
USJ1 = {USJ_U1, USJ_S1, {USJ_JU1, USJ_JS1, USJ_JR1}},
lists:foreach(
fun(Group) ->
Group2 = list_to_binary(Group),
gen_storage:write(Host,
#rostergroup{user_host_jid = USJ1,
grp = Group2})
end, Groups),
Name2 = convert_name_to_exmpp(Name),
AskMessage2 = convert_askmessage_to_exmpp(AskMessage),
#rosteritem{user_host_jid = USJ1,
name = Name2,
subscription = Subscription,
ask = Ask,
askmessage = AskMessage2}
end}
]);
%% @hidden
%% Convert roster table to support virtual host
convert_table1(Fields) ->
?INFO_MSG("Virtual host support: converting roster table from "
"{uj, user, jid, name, subscription, ask, groups, xattrs, xs} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_roster_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, roster},
{attributes, record_info(fields, roster)}]),
mnesia:del_table_index(roster, user),
mnesia:transform_table(roster, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_roster_tmp_table),
mnesia:foldl(
fun(#roster{usj = {U, {JID_U, JID_S, JID_R}}, us = U, xs = XS, askmessage = AM} = R, _) ->
U1 = convert_jid_to_exmpp(U),
JID_U1 = convert_jid_to_exmpp(JID_U),
JID_R1 = convert_jid_to_exmpp(JID_R),
JID1 = {JID_U1, JID_S, JID_R1},
XS1 = convert_xs_to_exmpp(XS),
AM1 = convert_askmessage_to_exmpp(AM),
mnesia:dirty_write(
mod_roster_tmp_table,
R#roster{usj = {U1, Host, JID1},
us = {U1, Host}, xs = XS1,
askmessage = AM1})
end, ok, roster)
end,
mnesia:transaction(F1),
mnesia:clear_table(roster),
F2 = fun() ->
mnesia:write_lock_table(roster),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_roster_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_roster_tmp_table).
%% @hidden
%% Convert roster table: xattrs fields become
convert_table2(Fields) ->
?INFO_MSG("Converting roster table from "
"{usj, us, jid, name, subscription, ask, groups, xattrs, xs} format", []),
mnesia:transform_table(roster, ignore, Fields),
convert_to_exmpp().
%% @hidden
convert_to_exmpp() ->
Fun = fun() ->
case mnesia:first(roster) of
{_User, Server, _JID} when is_binary(Server) ->
none;
{_User, Server, _JID} when is_list(Server) ->
mnesia:foldl(fun convert_to_exmpp2/2,
done, roster, write);
'$end_of_table' ->
none
end
end,
mnesia:transaction(Fun).
%% @hidden
convert_to_exmpp2(#roster{
usj = {USJ_U, USJ_S, {USJ_JU, USJ_JS, USJ_JR}} = Key,
us = {US_U, US_S},
jid = {JID_U, JID_S, JID_R},
name = N, xs = XS, groups = G, askmessage = AM} = R, Acc) ->
% Remove old entry.
mnesia:delete({roster, Key}),
% Convert "" to undefined in JIDs and string() to binary().
USJ_U1 = convert_jid_to_exmpp(USJ_U),
USJ_S1 = convert_jid_to_exmpp(USJ_S),
USJ_JU1 = convert_jid_to_exmpp(USJ_JU),
USJ_JS1 = convert_jid_to_exmpp(USJ_JS),
USJ_JR1 = convert_jid_to_exmpp(USJ_JR),
US_U1 = convert_jid_to_exmpp(US_U),
US_S1 = convert_jid_to_exmpp(US_S),
JID_U1 = convert_jid_to_exmpp(JID_U),
JID_S1 = convert_jid_to_exmpp(JID_S),
JID_R1 = convert_jid_to_exmpp(JID_R),
% Convert name.
N1 = convert_name_to_exmpp(N),
% Convert groups.
G1 = convert_groups_to_exmpp(G, []),
% Convert xs.
XS1 = convert_xs_to_exmpp(XS),
% Convert askmessage.
AM1 = convert_askmessage_to_exmpp(AM),
% Prepare the new record.
New_R = R#roster{
usj = {USJ_U1, USJ_S1, {USJ_JU1, USJ_JS1, USJ_JR1}},
us = {US_U1, US_S1},
jid = {JID_U1, JID_S1, JID_R1},
name = N1, groups = G1, xs = XS1, askmessage = AM1},
% Write the new record.
mnesia:write(New_R),
Acc.
%% @hidden
update_table(Host, odbc) ->
gen_storage_migration:migrate_odbc(
Host, [rosteritem, rostergroup],
[{"rosterusers", ["username", "jid", "nick",
"subscription", "ask", "askmessage",
"server", "subscribe", "type"],
fun(SELECT,
Username, JID, Nick,
Subscription, Ask, AskMessage,
Server, Subscribe, Type) ->
USJ = {Username, Host, JID},
[#rosteritem{user_host_jid = USJ,
name = Nick,
subscription = case Subscription of
"B" -> both;
"T" -> to;
"F" -> from;
_ -> none
end,
ask = case Ask of
"S" -> subscribe;
"U" -> unsubscribe;
"B" -> both;
"O" -> out;
"I" -> in;
_ -> none
end,
askmessage = AskMessage}
| [#rostergroup{user_host_jid = USJ,
grp = Group}
|| [Group] <- SELECT(["grp"], "rostergroups", [{"username", Username},
{"jid", JID}])]]
end}]),
ejabberd_odbc:sql_query(Host, "DROP TABLE rostergroups").
convert_jid_to_exmpp("") -> undefined;
convert_jid_to_exmpp(V) when is_list(V) -> list_to_binary(V).
%% @hidden
convert_name_to_exmpp(N) when is_list(N) -> list_to_binary(N).
%% @hidden
convert_groups_to_exmpp([G | Rest], New_G) ->
convert_groups_to_exmpp(Rest, [list_to_binary(G) | New_G]);
convert_groups_to_exmpp([], New_G) ->
lists:reverse(New_G).
%% @hidden
convert_xs_to_exmpp(Els) ->
convert_xs_to_exmpp(Els, []).
%% @hidden
convert_xs_to_exmpp([El | Rest], Result) ->
New_El = exmpp_xml:xmlelement_to_xmlel(El,
[?NS_JABBER_CLIENT], [{?NS_XMPP, ?NS_XMPP_pfx}]),
convert_xs_to_exmpp(Rest, [New_El | Result]);
convert_xs_to_exmpp([], Result) ->
lists:reverse(Result).
%% @hidden
convert_askmessage_to_exmpp(AM) when is_binary(AM) ->
AM;
convert_askmessage_to_exmpp(AM) ->
list_to_binary(AM).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% @spec (Acc, Host, Request) -> {stop, Result} | Acc
%% Acc = term()
%% Host = string()
@ -1381,14 +1354,14 @@ user_roster(User, Server, Query, Lang) ->
Groups =
lists:flatmap(
fun(Group) ->
[?C(binary_to_list(Group)), ?BR]
[?C(Group), ?BR]
end, R#roster.groups),
Pending = ask_to_pending(R#roster.ask),
TDJID = build_contact_jid_td(R#roster.jid),
?XE("tr",
[TDJID,
?XAC("td", [?XMLATTR('class', <<"valign">>)],
binary_to_list(R#roster.name)),
R#roster.name),
?XAC("td", [?XMLATTR('class', <<"valign">>)],
atom_to_list(R#roster.subscription)),
?XAC("td", [?XMLATTR('class', <<"valign">>)],
@ -1571,83 +1544,3 @@ us_to_list({User, Server}) ->
webadmin_user(Acc, _User, _Server, Lang) ->
% `Lang' is used by the `T' macro, called from the `ACT' macro.
Acc ++ [?XE("h3", [?ACT("roster/", "Roster")])].
update_tables(Host) ->
gen_storage_migration:migrate_mnesia(
Host, rosteritem,
[{roster, [uj, user, jid, name, subscription, ask, groups, xattrs, xs],
fun({roster, {User, JID}, _, _, Name, Subscription, Ask, Groups, _Xattrs, _Xs}) ->
USJ = {User, Host, JID},
lists:foreach(
fun(Group) ->
gen_storage:write(Host,
#rostergroup{user_server_jid = USJ,
grp = Group})
end, Groups),
#rosteritem{user_server_jid = USJ,
name = Name,
subscription = Subscription,
ask = Ask}
end},
{roster, [usj, us, jid, name, subscription, ask, groups, xattrs, xs],
fun({roster, USJ, _, _, Name, Subscription, Ask, Groups, _Xattrs, _Xs}) ->
lists:foreach(
fun(Group) ->
gen_storage:write(Host,
#rostergroup{user_server_jid = USJ,
grp = Group})
end, Groups),
#rosteritem{user_server_jid = USJ,
name = Name,
subscription = Subscription,
ask = Ask}
end},
{roster, [usj, us, jid, name, subscription, ask, groups, askmessage, xs],
fun({roster, USJ, _, _, Name, Subscription, Ask, Groups, AskMessage, _Xs}) ->
lists:foreach(
fun(Group) ->
gen_storage:write(Host,
#rostergroup{user_server_jid = USJ,
grp = Group})
end, Groups),
#rosteritem{user_server_jid = USJ,
name = Name,
subscription = Subscription,
ask = Ask,
askmessage = AskMessage}
end}
]),
gen_storage_migration:migrate_odbc(
Host, [rosteritem, rostergroup],
[{"rosterusers", ["username", "jid", "nick",
"subscription", "ask", "askmessage",
"server", "subscribe", "type"],
fun(SELECT,
Username, JID, Nick,
Subscription, Ask, AskMessage,
Server, Subscribe, Type) ->
USJ = {Username, Host, JID},
[#rosteritem{user_server_jid = USJ,
name = Nick,
subscription = case Subscription of
"B" -> both;
"T" -> to;
"F" -> from;
_ -> none
end,
ask = case Ask of
"S" -> subscribe;
"U" -> unsubscribe;
"B" -> both;
"O" -> out;
"I" -> in;
_ -> none
end,
askmessage = AskMessage}
| [#rostergroup{user_server_jid = USJ,
grp = Group}
|| [Group] <- SELECT(["grp"], "rostergroups", [{"username", Username},
{"jid", JID}])]]
end}]).

View File

@ -31,3 +31,26 @@
-record(roster_version, {us,
version}).
%% @type rosteritem() = {roster, USJ, US, Contact_JID, Name, Subscription, Ask, Groups, Askmessage, Xs}
%% USJ = {LUser, LServer, Prepd_Contact_JID}
%% LUser = binary()
%% LServer = binary()
%% Prepd_Contact_JID = jlib:shortjid()
%% US = {LUser, LServer}
%% Contact_JID = jlib:shortjid()
%% Name = binary()
%% Subscription = none | to | from | both
%% Ask = none | out | in | both
%% Groups = [binary()]
%% Askmessage = binary()
%% Xs = [exmpp_xml:xmlel()]
%% TODO: keep a non-prepped jid like in mnesia mod_roster?
-record(rosteritem, {user_host_jid,
name = <<"">>,
subscription = none,
ask = none,
askmessage = <<"">>}).
-record(rostergroup, {user_host_jid,
grp}).

View File

@ -24,6 +24,32 @@
%%%
%%%----------------------------------------------------------------------
%%% Database schema (version / storage / table)
%%%
%%% 2.1.x / mnesia / vcard
%%% us = {Username::string(), Host::string()}
%%% vcard = xmlelement()
%%%
%%% 2.1.x / odbc / vcard
%%% username = varchar250
%%% vcard = text
%%%
%%% 3.0.0-prealpha / mnesia / vcard
%%% us = {Username::string(), Host::string()}
%%% vcard = xmlel()
%%%
%%% 3.0.0-prealpha / odbc / vcard
%%% Same as 2.1.x
%%%
%%% 3.0.0-alpha / mnesia / vcard
%%% user_host = {Username::string(), Host::string()}
%%% vcard = xmlel()
%%%
%%% 3.0.0-alpha / odbc / vcard
%%% user = varchar150
%%% host = varchar150
%%% vcard = text
-module(mod_vcard).
-author('alexey@process-one.net').
@ -48,8 +74,8 @@
-define(JUD_MATCHES, 30).
-record(vcard_search, {user_server,
user, luser,
-record(vcard_search, {user_host,
username, lusername,
fn, lfn,
family, lfamily,
given, lgiven,
@ -62,7 +88,7 @@
orgname, lorgname,
orgunit, lorgunit
}).
-record(vcard, {user_server, vcard}).
-record(vcard, {user_host, vcard}).
-define(PROCNAME, ejabberd_mod_vcard).
@ -73,15 +99,14 @@ start(Host, Opts) ->
[{disc_only_copies, [node()]},
{odbc_host, Host},
{attributes, record_info(fields, vcard)},
{types, [{user_server, {text, text}}]}]),
{types, [{user_host, {text, text}}]}]),
gen_storage:create_table(Backend, HostB, vcard_search,
[{disc_copies, [node()]},
{odbc_host, Host},
{attributes, record_info(fields, vcard_search)},
{types, [{user_server, {text, text}},
{user, {text, text}}]}]),
update_tables(Host),
gen_storage:add_table_index(Host, vcard_search, luser),
{types, [{user_host, {text, text}}]}]),
update_tables(Host, Backend),
gen_storage:add_table_index(Host, vcard_search, lusername),
gen_storage:add_table_index(Host, vcard_search, lfn),
gen_storage:add_table_index(Host, vcard_search, lfamily),
gen_storage:add_table_index(Host, vcard_search, lgiven),
@ -195,24 +220,7 @@ process_local_iq(_From, _To, #iq{type = set} = IQ_Rec) ->
process_sm_iq(_From, To, #iq{type = get} = IQ_Rec) ->
LUser = exmpp_jid:prep_node_as_list(To),
LServer = exmpp_jid:prep_domain_as_list(To),
US = {LUser, LServer},
F = fun() ->
gen_storage:read(LServer, {vcard, US})
end,
Els = case gen_storage:transaction(LServer, vcard, F) of
{atomic, [#vcard{vcard = SVCARD} | _]} ->
case xml_stream:parse_element(SVCARD) of
{error, _Reason} ->
novcard;
VCARD ->
{vcard, VCARD}
end;
{atomic, []} ->
novcard;
{aborted, _Reason} ->
novcard
end,
case Els of
case get_vcard(LUser, LServer) of
{vcard, VCard} ->
exmpp_iq:result(IQ_Rec, VCard);
novcard ->
@ -229,6 +237,22 @@ process_sm_iq(From, _To, #iq{type = set, payload = Request} = IQ_Rec) ->
exmpp_iq:error(IQ_Rec, 'not-allowed')
end.
%% @spec (User::string(), Host::string()) -> {vcard, xmlel()} | novcard
get_vcard(User, Host) ->
US = {User, Host},
case gen_storage:dirty_read(Host, {vcard, US}) of
%% The vcard is stored as xmlel() in Mnesia:
[#vcard{vcard = VCARD}] when is_tuple(VCARD) ->
{vcard, VCARD};
%% The vcard is stored as a text string in ODBC:
[#vcard{vcard = SVCARD}] when is_list(SVCARD) ->
[VCARD] = exmpp_xml:parse_document(SVCARD, [names_as_atom]),
{vcard, VCARD};
[] ->
novcard
end.
set_vcard(User, LServer, VCARD) ->
FN = exmpp_xml:get_path(VCARD,
[{element, 'FN'}, cdata_as_list]),
@ -277,12 +301,16 @@ set_vcard(User, LServer, VCARD) ->
US = {LUser, LServer},
VcardToStore = case gen_storage:table_info(LServer, vcard, backend) of
mnesia -> VCARD;
odbc -> lists:flatten(exmpp_xml:document_to_list(VCARD))
end,
F = fun() ->
gen_storage:write(LServer, #vcard{user_server = US, vcard = VCARD}),
gen_storage:write(
#vcard_search{user_server=US,
user = {User, LServer},
luser = LUser,
gen_storage:write(LServer, #vcard{user_host = US, vcard = VcardToStore}),
gen_storage:write(LServer,
#vcard_search{user_host=US,
username = User, lusername = LUser,
fn = FN, lfn = LFN,
family = Family, lfamily = LFamily,
given = Given, lgiven = LGiven,
@ -501,7 +529,7 @@ search_result(Lang, JID, ServerHost, Data) ->
[#xmlcdata{cdata = Val}]}]}).
record_to_item(R) ->
{User, Server} = R#vcard_search.user,
{User, Server} = R#vcard_search.user_host,
#xmlel{ns = ?NS_DATA_FORMS, name = 'item', children =
[
?FIELD(<<"jid">>, list_to_binary(User ++ "@" ++ Server)),
@ -568,10 +596,10 @@ filter_fields([{SVar, [Val]} | Ds], Rules, LServer)
case gen_mod:get_module_opt(LServer, ?MODULE,
search_all_hosts, true) of
true ->
{like, luser, make_val(LVal)};
{like, lusername, make_val(LVal)};
false ->
Host = find_my_host(LServer),
{like, user_server, {make_val(LVal), Host}}
{like, user_host, {make_val(LVal), Host}}
end;
"fn" -> {like, lfn, make_val(LVal)};
"last" -> {like, lfamily, make_val(LVal)};
@ -628,7 +656,7 @@ parts_to_string(Parts) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
set_vcard_t(R, _) ->
US = R#vcard.user_server,
US = R#vcard.user_host,
User = US,
VCARD = R#vcard.vcard,
@ -669,8 +697,8 @@ set_vcard_t(R, _) ->
LOrgName = exmpp_stringprep:to_lower(OrgName),
LOrgUnit = exmpp_stringprep:to_lower(OrgUnit),
mnesia:write(
#vcard_search{user_server= US,
user = User, luser = LUser,
#vcard_search{user_host= US,
username = User, lusername = LUser,
fn = FN, lfn = LFN,
family = Family, lfamily = LFamily,
given = Given, lgiven = LGiven,
@ -707,174 +735,89 @@ remove_user(User, Server) when is_binary(User), is_binary(Server) ->
gen_storage:transaction(LServer, vcard, F).
update_tables(Host) ->
update_vcard_table(),
update_vcard_search_table(),
update_vcard_storage(Host).
%%%
%%% Update tables
%%%
update_vcard_table() ->
Fields = record_info(fields, vcard),
case mnesia:table_info(vcard, attributes) of
Fields ->
convert_to_exmpp();
[user, vcard] ->
?INFO_MSG("Converting vcard table from "
"{user, vcard} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_vcard_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, vcard},
{attributes, record_info(fields, vcard)}]),
mnesia:transform_table(vcard, ignore, Fields),
F1 = fun() ->
mnesia:write_lock_table(mod_vcard_tmp_table),
mnesia:foldl(
fun(#vcard{user_server = U} = R, _) ->
mnesia:dirty_write(
mod_vcard_tmp_table,
R#vcard{user_server = {U, Host}})
end, ok, vcard)
end,
mnesia:transaction(F1),
mnesia:clear_table(vcard),
F2 = fun() ->
mnesia:write_lock_table(vcard),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_vcard_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_vcard_tmp_table);
_ ->
?INFO_MSG("Recreating vcard table", []),
mnesia:transform_table(vcard, ignore, Fields)
end.
update_tables(Host, mnesia) ->
gen_storage_migration:migrate_mnesia(
Host, vcard,
[{vcard, [us, vcard],
fun({vcard, US, Vcard}) ->
#vcard{user_host = US,
vcard = convert_vcard_element(Vcard)}
end}]),
gen_storage_migration:migrate_mnesia(
Host, vcard_search,
[{vcard_search, [us,
username, lusername,
fn, lfn,
family, lfamily,
given, lgiven,
middle, lmiddle,
nickname, lnickname,
bday, lbday,
ctry, lctry,
locality, llocality,
email, lemail,
orgname, lorgname,
orgunit, lorgunit],
fun(Record) ->
Record
end}]);
update_tables(Host, odbc) ->
gen_storage_migration:migrate_odbc(
Host, [vcard],
[{"vcard", ["username", "vcard"],
fun(_, Username, Vcard) ->
[#vcard{user_host = {Username, Host},
vcard = Vcard}]
end}]),
gen_storage_migration:migrate_odbc(
Host, [vcard_search],
[{"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"],
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{user_host = {LUser, Host},
username = User, lusername = 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}]).
convert_to_exmpp() ->
Fun = fun() ->
case mnesia:first(vcard) of
'$end_of_table' ->
none;
Key ->
case mnesia:read({vcard, Key}) of
[#vcard{vcard = #xmlel{}}] ->
none;
[#vcard{vcard = #xmlelement{}}] ->
mnesia:foldl(fun convert_to_exmpp2/2,
done, vcard, write)
end
end
end,
mnesia:transaction(Fun).
convert_to_exmpp2(#vcard{vcard = ElOld} = VCard, Acc) ->
El0 = exmpp_xml:xmlelement_to_xmlel(ElOld, [?NS_VCARD], []),
El = exmpp_xml:remove_whitespaces_deeply(El0),
mnesia:write(VCard#vcard{vcard = El}),
Acc.
update_vcard_search_table() ->
Fields = record_info(fields, vcard_search),
case mnesia:table_info(vcard_search, attributes) of
Fields ->
ok;
[user, luser,
fn, lfn,
family, lfamily,
given, lgiven,
middle, lmiddle,
nickname, lnickname,
bday, lbday,
ctry, lctry,
locality, llocality,
email, lemail,
orgname, lorgname,
orgunit, lorgunit] ->
?INFO_MSG("Converting vcard_search table from "
"{user, luser, fn, lfn, family, lfamily, given, lgiven, middle, lmiddle, nickname, lnickname, bday, lbday, ctry, lctry, locality, llocality, email, lemail, orgname, lorgname, orgunit, lorgunit} format", []),
Host = ?MYNAME,
{atomic, ok} = mnesia:create_table(
mod_vcard_tmp_table,
[{disc_only_copies, [node()]},
{type, bag},
{local_content, true},
{record_name, vcard_search},
{attributes, record_info(fields, vcard_search)}]),
F1 = fun() ->
mnesia:write_lock_table(mod_vcard_tmp_table),
mnesia:foldl(
fun({vcard_search,
User, LUser,
FN, LFN,
Family, LFamily,
Given, LGiven,
Middle, LMiddle,
Nickname, LNickname,
BDay, LBDay,
CTRY, LCTRY,
Locality, LLocality,
EMail, LEMail,
OrgName, LOrgName,
OrgUnit, LOrgUnit
}, _) ->
mnesia:dirty_write(
mod_vcard_tmp_table,
#vcard_search{
user_server= {LUser, Host},
user = {User, Host},
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, ok, vcard_search)
end,
mnesia:transaction(F1),
lists:foreach(fun(I) ->
mnesia:del_table_index(
vcard_search,
element(I, {vcard_search,
user, luser,
fn, lfn,
family, lfamily,
given, lgiven,
middle, lmiddle,
nickname, lnickname,
bday, lbday,
ctry, lctry,
locality, llocality,
email, lemail,
orgname, lorgname,
orgunit, lorgunit}))
end, mnesia:table_info(vcard_search, index)),
mnesia:clear_table(vcard_search),
mnesia:transform_table(vcard_search, ignore, Fields),
F2 = fun() ->
mnesia:write_lock_table(vcard_search),
mnesia:foldl(
fun(R, _) ->
mnesia:dirty_write(R)
end, ok, mod_vcard_tmp_table)
end,
mnesia:transaction(F2),
mnesia:delete_table(mod_vcard_tmp_table);
_ ->
?INFO_MSG("Recreating vcard_search table", []),
mnesia:transform_table(vcard_search, ignore, Fields)
end.
convert_vcard_element(Xmlelement) ->
Xmlel = exmpp_xml:xmlelement_to_xmlel(Xmlelement, [?NS_VCARD], []),
exmpp_xml:remove_whitespaces_deeply(Xmlel).
%%%
%%% WebAdmin
@ -898,16 +841,14 @@ webadmin_page(_, Host,
webadmin_page(Acc, _, _) -> Acc.
user_vcard(User, Server, Query, Lang) ->
US = {exmpp_stringprep:nodeprep(User), exmpp_stringprep:nameprep(Server)},
US = {LUser, LServer} = {exmpp_stringprep:nodeprep(User), exmpp_stringprep:nameprep(Server)},
Res = user_queue_parse_query(US, Query),
VcardString = case mnesia:dirty_read({vcard, US}) of
[Vcard] ->
Xml = Vcard#vcard.vcard,
VcardString = case get_vcard(LUser, LServer) of
{vcard, Xml} ->
XmlString = lists:flatten(exmpp_xml:document_to_list(Xml)),
Reduced = re:replace(XmlString, "<BINVAL>[^<]*", "<BINVAL>...", [global, {return, list}]),
try_indent(Reduced);
[] -> "no vcard";
_ -> "error getting vcard"
novcard -> "no vcard"
end,
[?XE('h1', [?CT("vCard")]),
?XE('h2', [?AC("../", us_to_list(US))])
@ -937,18 +878,16 @@ try_indent(String) ->
end.
get_user_photo(User, Host) ->
US = {User, Host},
case mnesia:dirty_read({vcard, US}) of
[VCard] ->
case exmpp_xml:get_path(VCard#vcard.vcard, [{element, "PHOTO"}, {element, "BINVAL"}, cdata_as_list]) of
case get_vcard(User, Host) of
{vcard, VCard} ->
case exmpp_xml:get_path(VCard, [{element, "PHOTO"}, {element, "BINVAL"}, cdata_as_list]) of
[] -> "no avatar";
BinVal -> case catch jlib:decode_base64(BinVal) of
{'EXIT', _} -> "error";
Decoded -> Decoded
end
end;
[] -> "no vcard";
_ -> "error getting vcard"
novcard -> "no vcard"
end.
user_queue_parse_query(US, Query) ->
@ -971,11 +910,11 @@ us_to_list({User, Server}) ->
exmpp_jid:to_list(User, Server).
webadmin_user(Acc, User, Server, Lang) ->
US = {exmpp_stringprep:nodeprep(User), exmpp_stringprep:nameprep(Server)},
VcardSize = case mnesia:dirty_read({vcard, US}) of
[Vcard] -> get_vcard_size(Vcard);
[] -> 0;
_ -> -1
LUser = exmpp_stringprep:nodeprep(User),
LServer = exmpp_stringprep:nameprep(Server),
VcardSize = case get_vcard(LUser, LServer) of
{vcard, Vcard} -> get_vcard_size(Vcard);
novcard -> 0
end,
FVcardSize = case VcardSize > 0 of
true -> [?AC("vcard/", integer_to_list(VcardSize))];
@ -988,7 +927,7 @@ webadmin_user(Acc, User, Server, Lang) ->
Acc ++ [?XCT('h3', "vCard size:")] ++ FVcardSize ++ [?CT(" characters. ")] ++ RemoveEl.
get_vcard_size(Vcard) ->
String = lists:flatten(exmpp_xml:document_to_list(Vcard#vcard.vcard)),
String = lists:flatten(exmpp_xml:document_to_list(Vcard)),
length(String).
webadmin_user_parse_query(_, "removevcard", User, Server, _Query) ->
@ -1002,86 +941,3 @@ webadmin_user_parse_query(_, "removevcard", User, Server, _Query) ->
end;
webadmin_user_parse_query(Acc, _Action, _User, _Server, _Query) ->
Acc.
update_vcard_storage(Host) ->
%% TODO: vcard_search
gen_storage_migration:migrate_mnesia(
Host, vcard,
[{vcard, [user, vcard],
fun({vcard, U, Vcard}) ->
#vcard{user_server = {U, Host},
vcard = lists:flatten(xml:element_to_string(Vcard))}
end},
{vcard, [us, vcard],
fun({vcard, US, Vcard}) ->
#vcard{user_server = US,
vcard = lists:flatten(xml:element_to_string(Vcard))}
end}]),
gen_storage_migration:migrate_mnesia(
Host, vcard_search,
[{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],
fun(Record) ->
Record
end}]),
gen_storage_migration:migrate_odbc(
Host, [vcard],
[{"vcard", ["username", "vcard"],
fun(_, Username, Vcard) ->
[#vcard{user_server = {Username, Host},
vcard = Vcard}]
end}]),
gen_storage_migration:migrate_odbc(
Host, [vcard_search],
[{"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"],
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{user_server = {LUser, Host},
user = {User, Host},
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}]).