mirror of
https://github.com/processone/ejabberd.git
synced 2024-06-16 22:05:29 +02:00
![Holger Weiss](/assets/img/avatar_default.png)
If a user is subscribed to a contact but not vice versa, don't store the contact's CAPS. This makes sure no PEP items are leaked to the contact.
553 lines
18 KiB
Erlang
553 lines
18 KiB
Erlang
%%%----------------------------------------------------------------------
|
|
%%% File : mod_caps.erl
|
|
%%% Author : Magnus Henoch <henoch@dtek.chalmers.se>
|
|
%%% Purpose : Request and cache Entity Capabilities (XEP-0115)
|
|
%%% Created : 7 Oct 2006 by Magnus Henoch <henoch@dtek.chalmers.se>
|
|
%%%
|
|
%%%
|
|
%%% ejabberd, Copyright (C) 2002-2018 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.
|
|
%%%
|
|
%%% 2009, improvements from ProcessOne to support correct PEP handling
|
|
%%% through s2s, use less memory, and speedup global caps handling
|
|
%%%----------------------------------------------------------------------
|
|
|
|
-module(mod_caps).
|
|
|
|
-author('henoch@dtek.chalmers.se').
|
|
|
|
-protocol({xep, 115, '1.5'}).
|
|
|
|
-behaviour(gen_server).
|
|
|
|
-behaviour(gen_mod).
|
|
|
|
-export([read_caps/1, list_features/1, caps_stream_features/2,
|
|
disco_features/5, disco_identity/5, disco_info/5,
|
|
get_features/2, export/1, import_info/0, import/5,
|
|
get_user_caps/2, import_start/2, import_stop/2]).
|
|
|
|
%% gen_mod callbacks
|
|
-export([start/2, stop/1, reload/3, depends/2]).
|
|
|
|
%% gen_server callbacks
|
|
-export([init/1, handle_info/2, handle_call/3,
|
|
handle_cast/2, terminate/2, code_change/3]).
|
|
|
|
-export([user_send_packet/1, user_receive_packet/1,
|
|
c2s_presence_in/2, mod_opt_type/1, mod_options/1]).
|
|
|
|
-include("ejabberd.hrl").
|
|
-include("logger.hrl").
|
|
|
|
-include("xmpp.hrl").
|
|
-include("mod_caps.hrl").
|
|
|
|
-define(BAD_HASH_LIFETIME, 600).
|
|
|
|
-record(state, {host = <<"">> :: binary()}).
|
|
|
|
-callback init(binary(), gen_mod:opts()) -> any().
|
|
-callback import(binary(), {binary(), binary()}, [binary() | pos_integer()]) -> ok.
|
|
-callback caps_read(binary(), {binary(), binary()}) ->
|
|
{ok, non_neg_integer() | [binary()]} | error.
|
|
-callback caps_write(binary(), {binary(), binary()},
|
|
non_neg_integer() | [binary()]) -> any().
|
|
|
|
start(Host, Opts) ->
|
|
gen_mod:start_child(?MODULE, Host, Opts).
|
|
|
|
stop(Host) ->
|
|
gen_mod:stop_child(?MODULE, Host).
|
|
|
|
-spec get_features(binary(), nothing | caps()) -> [binary()].
|
|
get_features(_Host, nothing) -> [];
|
|
get_features(Host, #caps{node = Node, version = Version,
|
|
exts = Exts}) ->
|
|
SubNodes = [Version | Exts],
|
|
lists:foldl(fun (SubNode, Acc) ->
|
|
NodePair = {Node, SubNode},
|
|
case ets_cache:lookup(caps_features_cache, NodePair,
|
|
caps_read_fun(Host, NodePair))
|
|
of
|
|
{ok, Features} when is_list(Features) ->
|
|
Features ++ Acc;
|
|
_ -> Acc
|
|
end
|
|
end,
|
|
[], SubNodes).
|
|
|
|
-spec list_features(ejabberd_c2s:state()) -> [{ljid(), caps()}].
|
|
list_features(C2SState) ->
|
|
Rs = maps:get(caps_resources, C2SState, gb_trees:empty()),
|
|
gb_trees:to_list(Rs).
|
|
|
|
-spec get_user_caps(jid(), ejabberd_c2s:state()) -> {ok, caps()} | error.
|
|
get_user_caps(JID, C2SState) ->
|
|
Rs = maps:get(caps_resources, C2SState, gb_trees:empty()),
|
|
LJID = jid:tolower(JID),
|
|
case gb_trees:lookup(LJID, Rs) of
|
|
{value, Caps} ->
|
|
{ok, Caps};
|
|
none ->
|
|
error
|
|
end.
|
|
|
|
-spec read_caps(#presence{}) -> nothing | caps().
|
|
read_caps(Presence) ->
|
|
case xmpp:get_subtag(Presence, #caps{}) of
|
|
false -> nothing;
|
|
Caps -> Caps
|
|
end.
|
|
|
|
-spec user_send_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
|
|
user_send_packet({#presence{type = available,
|
|
from = #jid{luser = U, lserver = LServer} = From,
|
|
to = #jid{luser = U, lserver = LServer,
|
|
lresource = <<"">>}} = Pkt,
|
|
#{jid := To} = State}) ->
|
|
case read_caps(Pkt) of
|
|
nothing -> ok;
|
|
#caps{version = Version, exts = Exts} = Caps ->
|
|
feature_request(LServer, From, To, Caps, [Version | Exts])
|
|
end,
|
|
{Pkt, State};
|
|
user_send_packet(Acc) ->
|
|
Acc.
|
|
|
|
-spec user_receive_packet({stanza(), ejabberd_c2s:state()}) -> {stanza(), ejabberd_c2s:state()}.
|
|
user_receive_packet({#presence{from = From, type = available} = Pkt,
|
|
#{lserver := LServer, jid := To} = State}) ->
|
|
IsRemote = not ejabberd_router:is_my_host(From#jid.lserver),
|
|
if IsRemote ->
|
|
case read_caps(Pkt) of
|
|
nothing -> ok;
|
|
#caps{version = Version, exts = Exts} = Caps ->
|
|
feature_request(LServer, To, From, Caps, [Version | Exts])
|
|
end;
|
|
true -> ok
|
|
end,
|
|
{Pkt, State};
|
|
user_receive_packet(Acc) ->
|
|
Acc.
|
|
|
|
-spec caps_stream_features([xmpp_element()], binary()) -> [xmpp_element()].
|
|
caps_stream_features(Acc, MyHost) ->
|
|
case gen_mod:is_loaded(MyHost, ?MODULE) of
|
|
true ->
|
|
case make_my_disco_hash(MyHost) of
|
|
<<"">> ->
|
|
Acc;
|
|
Hash ->
|
|
[#caps{hash = <<"sha-1">>, node = ?EJABBERD_URI,
|
|
version = Hash} | Acc]
|
|
end;
|
|
false ->
|
|
Acc
|
|
end.
|
|
|
|
-spec disco_features({error, stanza_error()} | {result, [binary()]} | empty,
|
|
jid(), jid(),
|
|
binary(), binary()) ->
|
|
{error, stanza_error()} | {result, [binary()]} | empty.
|
|
disco_features(Acc, From, To, Node, Lang) ->
|
|
case is_valid_node(Node) of
|
|
true ->
|
|
ejabberd_hooks:run_fold(disco_local_features,
|
|
To#jid.lserver, empty,
|
|
[From, To, <<"">>, Lang]);
|
|
false ->
|
|
Acc
|
|
end.
|
|
|
|
-spec disco_identity([identity()], jid(), jid(),
|
|
binary(), binary()) ->
|
|
[identity()].
|
|
disco_identity(Acc, From, To, Node, Lang) ->
|
|
case is_valid_node(Node) of
|
|
true ->
|
|
ejabberd_hooks:run_fold(disco_local_identity,
|
|
To#jid.lserver, [],
|
|
[From, To, <<"">>, Lang]);
|
|
false ->
|
|
Acc
|
|
end.
|
|
|
|
-spec disco_info([xdata()], binary(), module(), binary(), binary()) -> [xdata()];
|
|
([xdata()], jid(), jid(), binary(), binary()) -> [xdata()].
|
|
disco_info(Acc, Host, Module, Node, Lang) when is_atom(Module) ->
|
|
case is_valid_node(Node) of
|
|
true ->
|
|
ejabberd_hooks:run_fold(disco_info, Host, [],
|
|
[Host, Module, <<"">>, Lang]);
|
|
false ->
|
|
Acc
|
|
end;
|
|
disco_info(Acc, _, _, _Node, _Lang) ->
|
|
Acc.
|
|
|
|
-spec c2s_presence_in(ejabberd_c2s:state(), presence()) -> ejabberd_c2s:state().
|
|
c2s_presence_in(C2SState,
|
|
#presence{from = From, to = To, type = Type} = Presence) ->
|
|
{Subscription, _, _} = ejabberd_hooks:run_fold(
|
|
roster_get_jid_info, To#jid.lserver,
|
|
{none, none, []},
|
|
[To#jid.luser, To#jid.lserver, From]),
|
|
ToSelf = (From#jid.luser == To#jid.luser)
|
|
and (From#jid.lserver == To#jid.lserver),
|
|
Insert = (Type == available)
|
|
and ((Subscription == both) or (Subscription == from) or ToSelf),
|
|
Delete = (Type == unavailable) or (Type == error),
|
|
if Insert or Delete ->
|
|
LFrom = jid:tolower(From),
|
|
Rs = maps:get(caps_resources, C2SState, gb_trees:empty()),
|
|
Caps = read_caps(Presence),
|
|
NewRs = case Caps of
|
|
nothing when Insert == true -> Rs;
|
|
_ when Insert == true ->
|
|
case gb_trees:lookup(LFrom, Rs) of
|
|
{value, Caps} -> Rs;
|
|
none ->
|
|
ejabberd_hooks:run(caps_add, To#jid.lserver,
|
|
[From, To,
|
|
get_features(To#jid.lserver, Caps)]),
|
|
gb_trees:insert(LFrom, Caps, Rs);
|
|
_ ->
|
|
ejabberd_hooks:run(caps_update, To#jid.lserver,
|
|
[From, To,
|
|
get_features(To#jid.lserver, Caps)]),
|
|
gb_trees:update(LFrom, Caps, Rs)
|
|
end;
|
|
_ -> gb_trees:delete_any(LFrom, Rs)
|
|
end,
|
|
C2SState#{caps_resources => NewRs};
|
|
true ->
|
|
C2SState
|
|
end.
|
|
|
|
-spec depends(binary(), gen_mod:opts()) -> [{module(), hard | soft}].
|
|
depends(_Host, _Opts) ->
|
|
[].
|
|
|
|
reload(Host, NewOpts, OldOpts) ->
|
|
NewMod = gen_mod:db_mod(Host, NewOpts, ?MODULE),
|
|
OldMod = gen_mod:db_mod(Host, OldOpts, ?MODULE),
|
|
if OldMod /= NewMod ->
|
|
NewMod:init(Host, NewOpts);
|
|
true ->
|
|
ok
|
|
end,
|
|
case gen_mod:is_equal_opt(cache_size, NewOpts, OldOpts) of
|
|
{false, MaxSize, _} ->
|
|
ets_cache:setopts(caps_features_cache, [{max_size, MaxSize}]),
|
|
ets_cache:setopts(caps_requests_cache, [{max_size, MaxSize}]);
|
|
true ->
|
|
ok
|
|
end,
|
|
case gen_mod:is_equal_opt(cache_life_time, NewOpts, OldOpts) of
|
|
{false, Time, _} ->
|
|
LifeTime = case Time of
|
|
infinity -> infinity;
|
|
_ -> timer:seconds(Time)
|
|
end,
|
|
ets_cache:setopts(caps_features_cache, [{life_time, LifeTime}]);
|
|
true ->
|
|
ok
|
|
end.
|
|
|
|
init([Host, Opts]) ->
|
|
process_flag(trap_exit, true),
|
|
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
|
|
init_cache(Opts),
|
|
Mod:init(Host, Opts),
|
|
ejabberd_hooks:add(c2s_presence_in, Host, ?MODULE,
|
|
c2s_presence_in, 75),
|
|
ejabberd_hooks:add(user_send_packet, Host, ?MODULE,
|
|
user_send_packet, 75),
|
|
ejabberd_hooks:add(user_receive_packet, Host, ?MODULE,
|
|
user_receive_packet, 75),
|
|
ejabberd_hooks:add(c2s_post_auth_features, Host, ?MODULE,
|
|
caps_stream_features, 75),
|
|
ejabberd_hooks:add(s2s_in_post_auth_features, Host, ?MODULE,
|
|
caps_stream_features, 75),
|
|
ejabberd_hooks:add(disco_local_features, Host, ?MODULE,
|
|
disco_features, 75),
|
|
ejabberd_hooks:add(disco_local_identity, Host, ?MODULE,
|
|
disco_identity, 75),
|
|
ejabberd_hooks:add(disco_info, Host, ?MODULE,
|
|
disco_info, 75),
|
|
{ok, #state{host = Host}}.
|
|
|
|
handle_call(stop, _From, State) ->
|
|
{stop, normal, ok, State};
|
|
handle_call(_Req, _From, State) ->
|
|
{reply, {error, badarg}, State}.
|
|
|
|
handle_cast(_Msg, State) -> {noreply, State}.
|
|
|
|
handle_info({iq_reply, IQReply, {Host, From, To, Caps, SubNodes}}, State) ->
|
|
feature_response(IQReply, Host, From, To, Caps, SubNodes),
|
|
{noreply, State};
|
|
handle_info(Info, State) ->
|
|
?WARNING_MSG("unexpected info: ~p", [Info]),
|
|
{noreply, State}.
|
|
|
|
terminate(_Reason, State) ->
|
|
Host = State#state.host,
|
|
ejabberd_hooks:delete(c2s_presence_in, Host, ?MODULE,
|
|
c2s_presence_in, 75),
|
|
ejabberd_hooks:delete(user_send_packet, Host, ?MODULE,
|
|
user_send_packet, 75),
|
|
ejabberd_hooks:delete(user_receive_packet, Host,
|
|
?MODULE, user_receive_packet, 75),
|
|
ejabberd_hooks:delete(c2s_post_auth_features, Host,
|
|
?MODULE, caps_stream_features, 75),
|
|
ejabberd_hooks:delete(s2s_in_post_auth_features, Host,
|
|
?MODULE, caps_stream_features, 75),
|
|
ejabberd_hooks:delete(disco_local_features, Host,
|
|
?MODULE, disco_features, 75),
|
|
ejabberd_hooks:delete(disco_local_identity, Host,
|
|
?MODULE, disco_identity, 75),
|
|
ejabberd_hooks:delete(disco_info, Host, ?MODULE,
|
|
disco_info, 75),
|
|
ok.
|
|
|
|
code_change(_OldVsn, State, _Extra) -> {ok, State}.
|
|
|
|
-spec feature_request(binary(), jid(), jid(), caps(), [binary()]) -> any().
|
|
feature_request(Host, From, To, Caps,
|
|
[SubNode | Tail] = SubNodes) ->
|
|
Node = Caps#caps.node,
|
|
NodePair = {Node, SubNode},
|
|
case ets_cache:lookup(caps_features_cache, NodePair,
|
|
caps_read_fun(Host, NodePair)) of
|
|
{ok, Fs} when is_list(Fs) ->
|
|
feature_request(Host, From, To, Caps, Tail);
|
|
_ ->
|
|
LTo = jid:tolower(To),
|
|
case ets_cache:insert_new(caps_requests_cache, {LTo, NodePair}, ok) of
|
|
true ->
|
|
IQ = #iq{type = get,
|
|
from = From,
|
|
to = To,
|
|
sub_els = [#disco_info{node = <<Node/binary, "#",
|
|
SubNode/binary>>}]},
|
|
ejabberd_router:route_iq(
|
|
IQ, {Host, From, To, Caps, SubNodes},
|
|
gen_mod:get_module_proc(Host, ?MODULE));
|
|
false ->
|
|
ok
|
|
end,
|
|
feature_request(Host, From, To, Caps, Tail)
|
|
end;
|
|
feature_request(_Host, _From, _To, _Caps, []) -> ok.
|
|
|
|
-spec feature_response(iq(), binary(), jid(), jid(), caps(), [binary()]) -> any().
|
|
feature_response(#iq{type = result, sub_els = [El]},
|
|
Host, From, To, Caps, [SubNode | SubNodes]) ->
|
|
NodePair = {Caps#caps.node, SubNode},
|
|
try
|
|
DiscoInfo = xmpp:decode(El),
|
|
case check_hash(Caps, DiscoInfo) of
|
|
true ->
|
|
Features = DiscoInfo#disco_info.features,
|
|
LServer = jid:nameprep(Host),
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
case Mod:caps_write(LServer, NodePair, Features) of
|
|
ok ->
|
|
ets_cache:delete(caps_features_cache, NodePair);
|
|
{error, _} ->
|
|
ok
|
|
end;
|
|
false -> ok
|
|
end
|
|
catch _:{xmpp_codec, _Why} ->
|
|
ok
|
|
end,
|
|
feature_request(Host, From, To, Caps, SubNodes);
|
|
feature_response(_IQResult, Host, From, To, Caps,
|
|
[_SubNode | SubNodes]) ->
|
|
feature_request(Host, From, To, Caps, SubNodes).
|
|
|
|
-spec caps_read_fun(binary(), {binary(), binary()})
|
|
-> fun(() -> {ok, [binary()] | non_neg_integer()} | error).
|
|
caps_read_fun(Host, Node) ->
|
|
LServer = jid:nameprep(Host),
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
fun() -> Mod:caps_read(LServer, Node) end.
|
|
|
|
-spec make_my_disco_hash(binary()) -> binary().
|
|
make_my_disco_hash(Host) ->
|
|
JID = jid:make(Host),
|
|
case {ejabberd_hooks:run_fold(disco_local_features,
|
|
Host, empty, [JID, JID, <<"">>, <<"">>]),
|
|
ejabberd_hooks:run_fold(disco_local_identity, Host, [],
|
|
[JID, JID, <<"">>, <<"">>]),
|
|
ejabberd_hooks:run_fold(disco_info, Host, [],
|
|
[Host, undefined, <<"">>, <<"">>])}
|
|
of
|
|
{{result, Features}, Identities, Info} ->
|
|
Feats = lists:map(fun ({{Feat, _Host}}) -> Feat;
|
|
(Feat) -> Feat
|
|
end,
|
|
Features),
|
|
DiscoInfo = #disco_info{identities = Identities,
|
|
features = Feats,
|
|
xdata = Info},
|
|
make_disco_hash(DiscoInfo, sha);
|
|
_Err -> <<"">>
|
|
end.
|
|
|
|
-type digest_type() :: md5 | sha | sha224 | sha256 | sha384 | sha512.
|
|
-spec make_disco_hash(disco_info(), digest_type()) -> binary().
|
|
make_disco_hash(DiscoInfo, Algo) ->
|
|
Concat = list_to_binary([concat_identities(DiscoInfo),
|
|
concat_features(DiscoInfo), concat_info(DiscoInfo)]),
|
|
base64:encode(case Algo of
|
|
md5 -> erlang:md5(Concat);
|
|
sha -> crypto:hash(sha, Concat);
|
|
sha224 -> crypto:hash(sha224, Concat);
|
|
sha256 -> crypto:hash(sha256, Concat);
|
|
sha384 -> crypto:hash(sha384, Concat);
|
|
sha512 -> crypto:hash(sha512, Concat)
|
|
end).
|
|
|
|
-spec check_hash(caps(), disco_info()) -> boolean().
|
|
check_hash(Caps, DiscoInfo) ->
|
|
case Caps#caps.hash of
|
|
<<"md5">> ->
|
|
Caps#caps.version == make_disco_hash(DiscoInfo, md5);
|
|
<<"sha-1">> ->
|
|
Caps#caps.version == make_disco_hash(DiscoInfo, sha);
|
|
<<"sha-224">> ->
|
|
Caps#caps.version == make_disco_hash(DiscoInfo, sha224);
|
|
<<"sha-256">> ->
|
|
Caps#caps.version == make_disco_hash(DiscoInfo, sha256);
|
|
<<"sha-384">> ->
|
|
Caps#caps.version == make_disco_hash(DiscoInfo, sha384);
|
|
<<"sha-512">> ->
|
|
Caps#caps.version == make_disco_hash(DiscoInfo, sha512);
|
|
_ -> true
|
|
end.
|
|
|
|
-spec concat_features(disco_info()) -> iolist().
|
|
concat_features(#disco_info{features = Features}) ->
|
|
lists:usort([[Feat, $<] || Feat <- Features]).
|
|
|
|
-spec concat_identities(disco_info()) -> iolist().
|
|
concat_identities(#disco_info{identities = Identities}) ->
|
|
lists:sort(
|
|
[[Cat, $/, T, $/, Lang, $/, Name, $<] ||
|
|
#identity{category = Cat, type = T,
|
|
lang = Lang, name = Name} <- Identities]).
|
|
|
|
-spec concat_info(disco_info()) -> iolist().
|
|
concat_info(#disco_info{xdata = Xs}) ->
|
|
lists:sort(
|
|
[concat_xdata_fields(X) || #xdata{type = result} = X <- Xs]).
|
|
|
|
-spec concat_xdata_fields(xdata()) -> iolist().
|
|
concat_xdata_fields(#xdata{fields = Fields} = X) ->
|
|
Form = xmpp_util:get_xdata_values(<<"FORM_TYPE">>, X),
|
|
Res = [[Var, $<, lists:sort([[Val, $<] || Val <- Values])]
|
|
|| #xdata_field{var = Var, values = Values} <- Fields,
|
|
is_binary(Var), Var /= <<"FORM_TYPE">>],
|
|
[Form, $<, lists:sort(Res)].
|
|
|
|
-spec is_valid_node(binary()) -> boolean().
|
|
is_valid_node(Node) ->
|
|
case str:tokens(Node, <<"#">>) of
|
|
[?EJABBERD_URI|_] ->
|
|
true;
|
|
_ ->
|
|
false
|
|
end.
|
|
|
|
init_cache(Opts) ->
|
|
CacheOpts = cache_opts(Opts),
|
|
case use_cache(Opts) of
|
|
true ->
|
|
ets_cache:new(caps_features_cache, CacheOpts);
|
|
false ->
|
|
ok
|
|
end,
|
|
CacheSize = proplists:get_value(max_size, CacheOpts),
|
|
ets_cache:new(caps_requests_cache,
|
|
[{max_size, CacheSize},
|
|
{life_time, timer:seconds(?BAD_HASH_LIFETIME)}]).
|
|
|
|
use_cache(Opts) ->
|
|
gen_mod:get_opt(use_cache, Opts).
|
|
|
|
cache_opts(Opts) ->
|
|
MaxSize = gen_mod:get_opt(cache_size, Opts),
|
|
CacheMissed = gen_mod:get_opt(cache_missed, Opts),
|
|
LifeTime = case gen_mod:get_opt(cache_life_time, Opts) of
|
|
infinity -> infinity;
|
|
I -> timer:seconds(I)
|
|
end,
|
|
[{max_size, MaxSize}, {cache_missed, CacheMissed}, {life_time, LifeTime}].
|
|
|
|
export(LServer) ->
|
|
Mod = gen_mod:db_mod(LServer, ?MODULE),
|
|
Mod:export(LServer).
|
|
|
|
import_info() ->
|
|
[{<<"caps_features">>, 4}].
|
|
|
|
import_start(LServer, DBType) ->
|
|
ets:new(caps_features_tmp, [private, named_table, bag]),
|
|
Mod = gen_mod:db_mod(DBType, ?MODULE),
|
|
Mod:init(LServer, []),
|
|
ok.
|
|
|
|
import(_LServer, {sql, _}, _DBType, <<"caps_features">>,
|
|
[Node, SubNode, Feature, _TimeStamp]) ->
|
|
Feature1 = case catch binary_to_integer(Feature) of
|
|
I when is_integer(I), I>0 -> I;
|
|
_ -> Feature
|
|
end,
|
|
ets:insert(caps_features_tmp, {{Node, SubNode}, Feature1}),
|
|
ok.
|
|
|
|
import_stop(LServer, DBType) ->
|
|
import_next(LServer, DBType, ets:first(caps_features_tmp)),
|
|
ets:delete(caps_features_tmp),
|
|
ok.
|
|
|
|
import_next(_LServer, _DBType, '$end_of_table') ->
|
|
ok;
|
|
import_next(LServer, DBType, NodePair) ->
|
|
Features = [F || {_, F} <- ets:lookup(caps_features_tmp, NodePair)],
|
|
Mod = gen_mod:db_mod(DBType, ?MODULE),
|
|
Mod:import(LServer, NodePair, Features),
|
|
import_next(LServer, DBType, ets:next(caps_features_tmp, NodePair)).
|
|
|
|
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(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end.
|
|
|
|
mod_options(Host) ->
|
|
[{db_type, ejabberd_config:default_db(Host, ?MODULE)},
|
|
{use_cache, ejabberd_config:use_cache(Host)},
|
|
{cache_size, ejabberd_config:cache_size(Host)},
|
|
{cache_missed, ejabberd_config:cache_missed(Host)},
|
|
{cache_life_time, ejabberd_config:cache_life_time(Host)}].
|