From 597c1c87d458052f10ccbdc610830f3a29c53f4f Mon Sep 17 00:00:00 2001 From: Badlop Date: Thu, 22 Jul 2010 18:49:12 +0200 Subject: [PATCH] Fix ejabberd modules --- src/ejabberd_auth_storage.erl | 414 +++++++++++++++++++++--------- src/mod_last.erl | 104 ++++---- src/mod_offline.erl | 354 ++++++++++---------------- src/mod_privacy.erl | 305 +++++++++++----------- src/mod_privacy.hrl | 2 +- src/mod_private.erl | 157 +++++------- src/mod_roster.erl | 439 ++++++++++++-------------------- src/mod_roster.hrl | 23 ++ src/mod_vcard.erl | 462 ++++++++++++---------------------- 9 files changed, 1043 insertions(+), 1217 deletions(-) diff --git a/src/ejabberd_auth_storage.erl b/src/ejabberd_auth_storage.erl index f69356432..a7514f8d2 100644 --- a/src/ejabberd_auth_storage.erl +++ b/src/ejabberd_auth_storage.erl @@ -2,10 +2,10 @@ %%% File : ejabberd_auth_storage.erl %%% Author : Alexey Shchepin , Stephan Maka %%% Purpose : Authentification via gen_storage -%%% Created : 16 Sep 2008 Stephan Maka +%%% Created : 12 Dec 2004 by Alexey Shchepin %%% %%% -%%% 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,201 +162,316 @@ 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}}])). -get_vh_registered_users(Server, [{from, Start}, {to, End}]) - when is_integer(Start) and is_integer(End) -> +%% @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: +%%
    +%%
  • `[{from, integer()}, {to, integer()}]'
  • +%%
  • `[{limit, integer()}, {offset, integer()}]'
  • +%%
  • `[{prefix, string()}]'
  • +%%
  • `[{prefix, string()}, {from, integer()}, {to, integer()}]'
  • +%%
  • `[{prefix, string()}, {limit, integer()}, {offset, integer()}]'
  • +%%
+ +get_vh_registered_users(Server, [{from, Start}, {to, 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) -> +get_vh_registered_users(Server, [{limit, Limit}, {offset, 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) -> +get_vh_registered_users(Server, [{prefix, 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) -> +get_vh_registered_users(Server, [{prefix, Prefix}, {from, Start}, {to, 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) -> +get_vh_registered_users(Server, [{prefix, Prefix}, {limit, Limit}, {offset, 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)], length(Set); - + 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}]). diff --git a/src/mod_last.erl b/src/mod_last.erl index 63705c86e..28832ca8d 100644 --- a/src/mod_last.erl +++ b/src/mod_last.erl @@ -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}]). diff --git a/src/mod_offline.erl b/src/mod_offline.erl index 2d4f5643c..d66f9c3eb 100644 --- a/src/mod_offline.erl +++ b/src/mod_offline.erl @@ -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. - diff --git a/src/mod_privacy.erl b/src/mod_privacy.erl index 378c504a3..d80e4e7d6 100644 --- a/src/mod_privacy.erl +++ b/src/mod_privacy.erl @@ -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">>. diff --git a/src/mod_privacy.hrl b/src/mod_privacy.hrl index 0e0b02b21..a6082ca48 100644 --- a/src/mod_privacy.hrl +++ b/src/mod_privacy.hrl @@ -19,7 +19,7 @@ %%% %%%---------------------------------------------------------------------- --record(privacy, {us, +-record(privacy, {user_host, default = none, lists = []}). diff --git a/src/mod_private.erl b/src/mod_private.erl index 5636e2ad8..396e84aec 100644 --- a/src/mod_private.erl +++ b/src/mod_private.erl @@ -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}]). diff --git a/src/mod_roster.erl b/src/mod_roster.erl index 6e93947d0..2ee73e1f6 100644 --- a/src/mod_roster.erl +++ b/src/mod_roster.erl @@ -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'). @@ -61,29 +128,6 @@ -include("mod_roster.hrl"). -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() @@ -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}]). - diff --git a/src/mod_roster.hrl b/src/mod_roster.hrl index c73af38f4..4b24a1efa 100644 --- a/src/mod_roster.hrl +++ b/src/mod_roster.hrl @@ -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}). diff --git a/src/mod_vcard.erl b/src/mod_vcard.erl index 97e8b711e..96aeb67c9 100644 --- a/src/mod_vcard.erl +++ b/src/mod_vcard.erl @@ -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, "[^<]*", "...", [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}]).