-include("mod_vcard.hrl").
-define(JUD_MATCHES, 30).
+-define(VCARD_CACHE, vcard_cache).
-callback init(binary(), gen_mod:opts()) -> any().
-callback stop(binary()) -> any().
-callback import(binary(), binary(), [binary()]) -> ok.
--callback get_vcard(binary(), binary()) -> [xmlel()] | error.
+-callback get_vcard(binary(), binary()) -> {ok, [xmlel()]} | error.
-callback set_vcard(binary(), binary(),
xmlel(), #vcard_search{}) -> {atomic, any()}.
-callback search_fields(binary()) -> [{binary(), binary()}].
infinity | pos_integer()) -> [{binary(), binary()}].
-callback remove_user(binary(), binary()) -> {atomic, any()}.
-callback is_search_supported(binary()) -> boolean().
+-callback use_cache(binary()) -> boolean().
+-callback cache_nodes(binary()) -> [node()].
+
+-optional_callbacks([use_cache/1, cache_nodes/1]).
-record(state, {host :: binary(), server_host :: binary()}).
process_flag(trap_exit, true),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
+ init_cache(Mod, Host, Opts),
ejabberd_hooks:add(remove_user, Host, ?MODULE,
remove_user, 50),
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
-spec get_vcard(binary(), binary()) -> [xmlel()] | error.
get_vcard(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:get_vcard(LUser, LServer).
+ Result = case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:lookup(
+ ?VCARD_CACHE, {LUser, LServer},
+ fun() -> Mod:get_vcard(LUser, LServer) end);
+ false ->
+ Mod:get_vcard(LUser, LServer)
+ end,
+ case Result of
+ {ok, Els} -> Els;
+ error -> error
+ end.
-spec make_vcard_search(binary(), binary(), binary(), xmlel()) -> #vcard_search{}.
make_vcard_search(User, LUser, LServer, VCARD) ->
VCardSearch = make_vcard_search(User, LUser, LServer, VCARD),
Mod = gen_mod:db_mod(LServer, ?MODULE),
Mod:set_vcard(LUser, LServer, VCARD, VCardSearch),
+ ets_cache:delete(?VCARD_CACHE, {LUser, LServer},
+ cache_nodes(Mod, LServer)),
ejabberd_hooks:run(vcard_set, LServer,
[LUser, LServer, VCARD])
end.
Mod:search(LServer, Data, AllowReturnAll, MaxMatch).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
--spec remove_user(binary(), binary()) -> any().
+-spec remove_user(binary(), binary()) -> ok.
remove_user(User, Server) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:remove_user(LUser, LServer).
+ Mod:remove_user(LUser, LServer),
+ ets_cache:delete(?VCARD_CACHE, {LUser, LServer}, cache_nodes(Mod, LServer)).
+
+-spec init_cache(module(), binary(), gen_mod:opts()) -> ok.
+init_cache(Mod, Host, Opts) ->
+ case use_cache(Mod, Host) of
+ true ->
+ CacheOpts = cache_opts(Host, Opts),
+ ets_cache:new(?VCARD_CACHE, CacheOpts);
+ false ->
+ ets_cache:delete(?VCARD_CACHE)
+ end.
+
+-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
+cache_opts(Host, Opts) ->
+ MaxSize = gen_mod:get_opt(
+ cache_size, Opts,
+ ejabberd_config:cache_size(Host)),
+ CacheMissed = gen_mod:get_opt(
+ cache_missed, Opts,
+ ejabberd_config:cache_missed(Host)),
+ LifeTime = case gen_mod:get_opt(
+ cache_life_time, Opts,
+ ejabberd_config:cache_life_time(Host)) of
+ infinity -> infinity;
+ I -> timer:seconds(I)
+ end,
+ [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
+
+-spec use_cache(module(), binary()) -> boolean().
+use_cache(Mod, Host) ->
+ case erlang:function_exported(Mod, use_cache, 1) of
+ true -> Mod:use_cache(Host);
+ false ->
+ gen_mod:get_module_opt(
+ Host, ?MODULE, use_cache,
+ ejabberd_config:use_cache(Host))
+ end.
+
+-spec cache_nodes(module(), binary()) -> [node()].
+cache_nodes(Mod, Host) ->
+ case erlang:function_exported(Mod, cache_nodes, 1) of
+ true -> Mod:cache_nodes(Host);
+ false -> ejabberd_cluster:get_nodes()
+ end.
import_info() ->
[{<<"vcard">>, 3}, {<<"vcard_search">>, 24}].
fun (B) when is_boolean(B) -> B end;
mod_opt_type(search_all_hosts) ->
fun (B) when is_boolean(B) -> B end;
+mod_opt_type(O) when O == cache_life_time; O == cache_size ->
+ fun (I) when is_integer(I), I > 0 -> I;
+ (infinity) -> infinity
+ end;
+mod_opt_type(O) when O == use_cache; O == cache_missed ->
+ fun (B) when is_boolean(B) -> B end;
mod_opt_type(_) ->
[allow_return_all, db_type, host, iqdisc, matches,
- search, search_all_hosts].
+ search, search_all_hosts, cache_life_time, cache_size,
+ use_cache, cache_missed].
#eldap_entry{attributes = Attributes} ->
VCard = ldap_attributes_to_vcard(Attributes, VCardMap,
{LUser, LServer}),
- [xmpp:encode(VCard)];
+ {ok, [xmpp:encode(VCard)]};
_ ->
- []
+ {ok, []}
end.
set_vcard(_LUser, _LServer, _VCard, _VCardSearch) ->
F = fun () -> mnesia:read({vcard, US}) end,
case mnesia:transaction(F) of
{atomic, Rs} ->
- lists:map(fun (R) -> R#vcard.vcard end, Rs);
+ {ok, lists:map(fun (R) -> R#vcard.vcard end, Rs)};
{aborted, _Reason} -> error
end.
get_vcard(LUser, LServer) ->
case ejabberd_riak:get(vcard, vcard_schema(), {LUser, LServer}) of
{ok, R} ->
- [R#vcard.vcard];
+ {ok, [R#vcard.vcard]};
{error, notfound} ->
- [];
+ {ok, []};
_ ->
error
end.
{selected, [{SVCARD}]} ->
case fxml_stream:parse_element(SVCARD) of
{error, _Reason} -> error;
- VCARD -> [VCARD]
+ VCARD -> {ok, [VCARD]}
end;
- {selected, []} -> [];
+ {selected, []} -> {ok, []};
_ -> error
end.
%% gen_mod callbacks
-export([start/2, stop/1, reload/3]).
--export([update_presence/1, vcard_set/3, export/1,
- import_info/0, import/5, import_start/2,
+-export([update_presence/1, vcard_set/3, remove_user/2,
mod_opt_type/1, depends/2]).
-include("ejabberd.hrl").
-include("logger.hrl").
-include("xmpp.hrl").
--callback init(binary(), gen_mod:opts()) -> any().
--callback import(binary(), binary(), [binary()]) -> ok.
--callback add_xupdate(binary(), binary(), binary()) -> {atomic, any()}.
--callback get_xupdate(binary(), binary()) -> binary() | undefined.
--callback remove_xupdate(binary(), binary()) -> {atomic, any()}.
+-define(VCARD_XUPDATE_CACHE, vcard_xupdate_cache).
%%====================================================================
%% gen_mod callbacks
%%====================================================================
start(Host, Opts) ->
- Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
- Mod:init(Host, Opts),
+ init_cache(Host, Opts),
ejabberd_hooks:add(c2s_self_presence, Host, ?MODULE,
update_presence, 100),
ejabberd_hooks:add(vcard_set, Host, ?MODULE, vcard_set,
100),
- ok.
+ ejabberd_hooks:add(remove_user, Host, ?MODULE, remove_user, 50).
stop(Host) ->
ejabberd_hooks:delete(c2s_self_presence, Host,
?MODULE, update_presence, 100),
ejabberd_hooks:delete(vcard_set, Host, ?MODULE,
vcard_set, 100),
- ok.
-
-reload(Host, NewOpts, OldOpts) ->
- NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
- OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE),
- if NewMod /= OldMod ->
- NewMod:init(Host, NewOpts);
- true ->
- ok
- end,
- ok.
+ ejabberd_hooks:delete(remove_user, Host, ?MODULE, remove_user, 50).
+
+reload(Host, NewOpts, _OldOpts) ->
+ init_cache(Host, NewOpts).
depends(_Host, _Opts) ->
- [].
+ [{mod_vcard, hard}].
%%====================================================================
%% Hooks
Acc.
-spec vcard_set(binary(), binary(), xmlel()) -> ok.
-vcard_set(LUser, LServer, VCARD) ->
- US = {LUser, LServer},
- case fxml:get_path_s(VCARD,
- [{elem, <<"PHOTO">>}, {elem, <<"BINVAL">>}, cdata])
- of
- <<>> -> remove_xupdate(LUser, LServer);
- BinVal ->
- add_xupdate(LUser, LServer,
- str:sha(misc:decode_base64(BinVal)))
- end,
- ejabberd_sm:force_update_presence(US).
+vcard_set(LUser, LServer, _VCARD) ->
+ ets_cache:delete(?VCARD_XUPDATE_CACHE, {LUser, LServer}),
+ ejabberd_sm:force_update_presence({LUser, LServer}).
+
+-spec remove_user(binary(), binary()) -> ok.
+remove_user(User, Server) ->
+ LUser = jid:nodeprep(User),
+ LServer = jid:nameprep(Server),
+ ets_cache:delete(?VCARD_XUPDATE_CACHE, {LUser, LServer}).
%%====================================================================
%% Storage
%%====================================================================
-
-add_xupdate(LUser, LServer, Hash) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:add_xupdate(LUser, LServer, Hash).
-
+-spec get_xupdate(binary(), binary()) -> binary() | undefined.
get_xupdate(LUser, LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:get_xupdate(LUser, LServer).
-
-remove_xupdate(LUser, LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:remove_xupdate(LUser, LServer).
-
-import_info() ->
- [{<<"vcard_xupdate">>, 3}].
-
-import_start(LServer, DBType) ->
- Mod = gen_mod:db_mod(DBType, ?MODULE),
- Mod:init(LServer, []).
-
-import(LServer, {sql, _}, DBType, Tab, [LUser, Hash, TimeStamp]) ->
- Mod = gen_mod:db_mod(DBType, ?MODULE),
- Mod:import(LServer, Tab, [LUser, Hash, TimeStamp]).
-
-export(LServer) ->
- Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:export(LServer).
+ Result = case use_cache(LServer) of
+ true ->
+ ets_cache:lookup(
+ ?VCARD_XUPDATE_CACHE, {LUser, LServer},
+ fun() -> db_get_xupdate(LUser, LServer) end);
+ false ->
+ db_get_xupdate(LUser, LServer)
+ end,
+ case Result of
+ {ok, Hash} -> Hash;
+ error -> undefined
+ end.
+
+-spec db_get_xupdate(binary(), binary()) -> {ok, binary()} | error.
+db_get_xupdate(LUser, LServer) ->
+ case mod_vcard:get_vcard(LUser, LServer) of
+ [VCard] ->
+ {ok, compute_hash(VCard)};
+ _ ->
+ error
+ end.
+
+-spec init_cache(binary(), gen_mod:opts()) -> ok.
+init_cache(Host, Opts) ->
+ case use_cache(Host) of
+ true ->
+ CacheOpts = cache_opts(Host, Opts),
+ ets_cache:new(?VCARD_XUPDATE_CACHE, CacheOpts);
+ false ->
+ ets_cache:delete(?VCARD_XUPDATE_CACHE)
+ end.
+
+-spec cache_opts(binary(), gen_mod:opts()) -> [proplists:property()].
+cache_opts(Host, Opts) ->
+ MaxSize = gen_mod:get_opt(
+ cache_size, Opts,
+ ejabberd_config:cache_size(Host)),
+ CacheMissed = gen_mod:get_opt(
+ cache_missed, Opts,
+ ejabberd_config:cache_missed(Host)),
+ LifeTime = case gen_mod:get_opt(
+ cache_life_time, Opts,
+ ejabberd_config:cache_life_time(Host)) of
+ infinity -> infinity;
+ I -> timer:seconds(I)
+ end,
+ [{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
+
+-spec use_cache(binary()) -> boolean().
+use_cache(Host) ->
+ gen_mod:get_module_opt(
+ Host, ?MODULE, use_cache,
+ ejabberd_config:use_cache(Host)).
+
+-spec compute_hash(xmlel()) -> binary().
+compute_hash(VCard) ->
+ case fxml:get_path_s(VCard,
+ [{elem, <<"PHOTO">>},
+ {elem, <<"BINVAL">>},
+ cdata]) of
+ <<>> ->
+ <<>>;
+ BinVal ->
+ str:sha(misc:decode_base64(BinVal))
+ end.
%%====================================================================
%% Options
%%====================================================================
-mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
-mod_opt_type(_) -> [db_type].
+mod_opt_type(O) when O == cache_life_time; O == cache_size ->
+ fun (I) when is_integer(I), I > 0 -> I;
+ (infinity) -> infinity
+ end;
+mod_opt_type(O) when O == use_cache; O == cache_missed ->
+ fun (B) when is_boolean(B) -> B end;
+mod_opt_type(_) ->
+ [cache_life_time, cache_size, use_cache, cache_missed].
+++ /dev/null
-%%%-------------------------------------------------------------------
-%%% File : mod_vcard_xupdate_mnesia.erl
-%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
-%%%
-%%% This program is free software; you can redistribute it and/or
-%%% modify it under the terms of the GNU General Public License as
-%%% published by the Free Software Foundation; either version 2 of the
-%%% License, or (at your option) any later version.
-%%%
-%%% This program is distributed in the hope that it will be useful,
-%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
-%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%%% General Public License for more details.
-%%%
-%%% You should have received a copy of the GNU General Public License along
-%%% with this program; if not, write to the Free Software Foundation, Inc.,
-%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-%%%
-%%%----------------------------------------------------------------------
-
--module(mod_vcard_xupdate_mnesia).
-
--behaviour(mod_vcard_xupdate).
-
-%% API
--export([init/2, import/3, add_xupdate/3, get_xupdate/2, remove_xupdate/2]).
--export([need_transform/1, transform/1]).
-
--include("mod_vcard_xupdate.hrl").
--include("logger.hrl").
-
-%%%===================================================================
-%%% API
-%%%===================================================================
-init(_Host, _Opts) ->
- ejabberd_mnesia:create(?MODULE, vcard_xupdate,
- [{disc_copies, [node()]},
- {attributes,
- record_info(fields, vcard_xupdate)}]).
-
-add_xupdate(LUser, LServer, Hash) ->
- F = fun () ->
- mnesia:write(#vcard_xupdate{us = {LUser, LServer},
- hash = Hash})
- end,
- mnesia:transaction(F).
-
-get_xupdate(LUser, LServer) ->
- case mnesia:dirty_read(vcard_xupdate, {LUser, LServer})
- of
- [#vcard_xupdate{hash = Hash}] -> Hash;
- _ -> undefined
- end.
-
-remove_xupdate(LUser, LServer) ->
- F = fun () ->
- mnesia:delete({vcard_xupdate, {LUser, LServer}})
- end,
- mnesia:transaction(F).
-
-import(LServer, <<"vcard_xupdate">>, [LUser, Hash, _TimeStamp]) ->
- mnesia:dirty_write(
- #vcard_xupdate{us = {LUser, LServer}, hash = Hash}).
-
-need_transform(#vcard_xupdate{us = {U, S}, hash = Hash})
- when is_list(U) orelse is_list(S) orelse is_list(Hash) ->
- ?INFO_MSG("Mnesia table 'vcard_xupdate' will be converted to binary", []),
- true;
-need_transform(_) ->
- false.
-
-transform(#vcard_xupdate{us = {U, S}, hash = Hash} = R) ->
- R#vcard_xupdate{us = {iolist_to_binary(U), iolist_to_binary(S)},
- hash = iolist_to_binary(Hash)}.
-
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
+++ /dev/null
-%%%-------------------------------------------------------------------
-%%% File : mod_vcard_xupdate_riak.erl
-%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
-%%%
-%%% This program is free software; you can redistribute it and/or
-%%% modify it under the terms of the GNU General Public License as
-%%% published by the Free Software Foundation; either version 2 of the
-%%% License, or (at your option) any later version.
-%%%
-%%% This program is distributed in the hope that it will be useful,
-%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
-%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%%% General Public License for more details.
-%%%
-%%% You should have received a copy of the GNU General Public License along
-%%% with this program; if not, write to the Free Software Foundation, Inc.,
-%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-%%%
-%%%----------------------------------------------------------------------
-
--module(mod_vcard_xupdate_riak).
-
--behaviour(mod_vcard_xupdate).
-
-%% API
--export([init/2, import/3, add_xupdate/3, get_xupdate/2, remove_xupdate/2]).
-
--include("mod_vcard_xupdate.hrl").
-
-%%%===================================================================
-%%% API
-%%%===================================================================
-init(_Host, _Opts) ->
- ok.
-
-add_xupdate(LUser, LServer, Hash) ->
- {atomic, ejabberd_riak:put(#vcard_xupdate{us = {LUser, LServer},
- hash = Hash},
- vcard_xupdate_schema())}.
-
-get_xupdate(LUser, LServer) ->
- case ejabberd_riak:get(vcard_xupdate, vcard_xupdate_schema(),
- {LUser, LServer}) of
- {ok, #vcard_xupdate{hash = Hash}} -> Hash;
- _ -> undefined
- end.
-
-remove_xupdate(LUser, LServer) ->
- {atomic, ejabberd_riak:delete(vcard_xupdate, {LUser, LServer})}.
-
-import(LServer, <<"vcard_xupdate">>, [LUser, Hash, _TimeStamp]) ->
- ejabberd_riak:put(
- #vcard_xupdate{us = {LUser, LServer}, hash = Hash},
- vcard_xupdate_schema()).
-
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
-vcard_xupdate_schema() ->
- {record_info(fields, vcard_xupdate), #vcard_xupdate{}}.
+++ /dev/null
-%%%-------------------------------------------------------------------
-%%% File : mod_vcard_xupdate_sql.erl
-%%% Author : Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%% Created : 13 Apr 2016 by Evgeny Khramtsov <ekhramtsov@process-one.net>
-%%%
-%%%
-%%% ejabberd, Copyright (C) 2002-2017 ProcessOne
-%%%
-%%% This program is free software; you can redistribute it and/or
-%%% modify it under the terms of the GNU General Public License as
-%%% published by the Free Software Foundation; either version 2 of the
-%%% License, or (at your option) any later version.
-%%%
-%%% This program is distributed in the hope that it will be useful,
-%%% but WITHOUT ANY WARRANTY; without even the implied warranty of
-%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-%%% General Public License for more details.
-%%%
-%%% You should have received a copy of the GNU General Public License along
-%%% with this program; if not, write to the Free Software Foundation, Inc.,
-%%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
-%%%
-%%%----------------------------------------------------------------------
-
--module(mod_vcard_xupdate_sql).
-
--compile([{parse_transform, ejabberd_sql_pt}]).
-
--behaviour(mod_vcard_xupdate).
-
-%% API
--export([init/2, import/3, add_xupdate/3, get_xupdate/2, remove_xupdate/2,
- export/1]).
-
--include("mod_vcard_xupdate.hrl").
--include("ejabberd_sql_pt.hrl").
-
-%%%===================================================================
-%%% API
-%%%===================================================================
-init(_Host, _Opts) ->
- ok.
-
-add_xupdate(LUser, LServer, Hash) ->
- F = fun () ->
- ?SQL_UPSERT_T(
- "vcard_xupdate",
- ["!username=%(LUser)s",
- "hash=%(Hash)s"])
- end,
- ejabberd_sql:sql_transaction(LServer, F).
-
-get_xupdate(LUser, LServer) ->
- case ejabberd_sql:sql_query(
- LServer,
- ?SQL("select @(hash)s from vcard_xupdate where"
- " username=%(LUser)s"))
- of
- {selected, [{Hash}]} -> Hash;
- _ -> undefined
- end.
-
-remove_xupdate(LUser, LServer) ->
- F = fun () ->
- ejabberd_sql:sql_query_t(
- ?SQL("delete from vcard_xupdate where username=%(LUser)s"))
- end,
- ejabberd_sql:sql_transaction(LServer, F).
-
-export(_Server) ->
- [{vcard_xupdate,
- fun(Host, #vcard_xupdate{us = {LUser, LServer}, hash = Hash})
- when LServer == Host ->
- [?SQL("delete from vcard_xupdate where username=%(LUser)s;"),
- ?SQL("insert into vcard_xupdate(username, hash) values ("
- "%(LUser)s, %(Hash)s);")];
- (_Host, _R) ->
- []
- end}].
-
-import(_, _, _) ->
- ok.
-
-%%%===================================================================
-%%% Internal functions
-%%%===================================================================
db_type: sql
mod_vcard:
db_type: sql
- mod_vcard_xupdate:
- db_type: sql
+ mod_vcard_xupdate: []
mod_carboncopy:
ram_db_type: sql
mod_adhoc: []
db_type: sql
mod_vcard:
db_type: sql
- mod_vcard_xupdate:
- db_type: sql
+ mod_vcard_xupdate: []
mod_carboncopy:
ram_db_type: sql
mod_adhoc: []
db_type: sql
mod_vcard:
db_type: sql
- mod_vcard_xupdate:
- db_type: sql
+ mod_vcard_xupdate: []
mod_carboncopy:
ram_db_type: sql
mod_adhoc: []
db_type: internal
mod_vcard:
db_type: internal
- mod_vcard_xupdate:
- db_type: internal
+ mod_vcard_xupdate: []
mod_carboncopy:
ram_db_type: internal
mod_client_state:
db_type: internal
mod_vcard:
db_type: internal
- mod_vcard_xupdate:
- db_type: internal
+ mod_vcard_xupdate: []
mod_carboncopy:
ram_db_type: redis
mod_client_state:
db_type: riak
mod_vcard:
db_type: riak
- mod_vcard_xupdate:
- db_type: riak
+ mod_vcard_xupdate: []
mod_carboncopy:
ram_db_type: riak
mod_adhoc: []