-include("mod_privacy.hrl").
-include("mod_last.hrl").
+-define(LAST_CACHE, last_activity_cache).
+
-callback init(binary(), gen_mod:opts()) -> any().
-callback import(binary(), #last_activity{}) -> ok | pass.
-callback get_last(binary(), binary()) ->
- {ok, non_neg_integer(), binary()} | not_found | {error, any()}.
--callback store_last_info(binary(), binary(), non_neg_integer(), binary()) -> any().
+ {ok, {non_neg_integer(), binary()}} | error | {error, any()}.
+-callback store_last_info(binary(), binary(), non_neg_integer(), binary()) -> ok | {error, any()}.
-callback remove_user(binary(), binary()) -> any().
+-callback use_cache(binary()) -> boolean().
+-callback cache_nodes(binary()) -> [node()].
+
+-optional_callbacks([use_cache/1, cache_nodes/1]).
start(Host, Opts) ->
IQDisc = gen_mod:get_opt(iqdisc, Opts, gen_iq_handler:iqdisc(Host)),
Mod = gen_mod:db_mod(Host, Opts, ?MODULE),
Mod:init(Host, Opts),
+ init_cache(Mod, Host, Opts),
gen_iq_handler:add_iq_handler(ejabberd_local, Host,
?NS_LAST, ?MODULE, process_local_iq, IQDisc),
gen_iq_handler:add_iq_handler(ejabberd_sm, Host,
true ->
ok
end,
+ init_cache(NewMod, Host, NewOpts),
case gen_mod:is_equal_opt(iqdisc, NewOpts, OldOpts, gen_iq_handler:iqdisc(Host)) of
{false, IQDisc, _} ->
gen_iq_handler:add_iq_handler(ejabberd_local, Host, ?NS_LAST,
privacy_check_packet(Acc, _, _, _) ->
Acc.
-%% @spec (LUser::string(), LServer::string()) ->
-%% {ok, TimeStamp::integer(), Status::string()} | not_found | {error, Reason}
-spec get_last(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
not_found | {error, any()}.
get_last(LUser, LServer) ->
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:get_last(LUser, LServer).
+ Res = case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:lookup(
+ ?LAST_CACHE, {LUser, LServer},
+ fun() -> Mod:get_last(LUser, LServer) end);
+ false ->
+ Mod:get_last(LUser, LServer)
+ end,
+ case Res of
+ {ok, {TimeStamp, Status}} -> {ok, TimeStamp, Status};
+ error -> not_found;
+ Err -> Err
+ end.
-spec get_last_iq(iq(), binary(), binary()) -> iq().
get_last_iq(#iq{lang = Lang} = IQ, LUser, LServer) ->
LUser = jid:nodeprep(User),
LServer = jid:nameprep(Server),
Mod = gen_mod:db_mod(LServer, ?MODULE),
- Mod:store_last_info(LUser, LServer, TimeStamp, Status).
+ case use_cache(Mod, LServer) of
+ true ->
+ ets_cache:update(
+ ?LAST_CACHE, {LUser, LServer}, {ok, {TimeStamp, Status}},
+ fun() ->
+ Mod:store_last_info(LUser, LServer, TimeStamp, Status)
+ end, cache_nodes(Mod, LServer));
+ false ->
+ Mod:store_last_info(LUser, LServer, TimeStamp, Status)
+ end.
-spec get_last_info(binary(), binary()) -> {ok, non_neg_integer(), binary()} |
not_found.
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(?LAST_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(?LAST_CACHE, CacheOpts);
+ false ->
+ ets_cache:delete(?LAST_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() ->
[{<<"last">>, 3}].
mod_opt_type(db_type) -> fun(T) -> ejabberd_config:v_db(?MODULE, T) end;
mod_opt_type(iqdisc) -> fun gen_iq_handler:check_type/1;
-mod_opt_type(_) -> [db_type, iqdisc].
+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, iqdisc, cache_life_time, cache_size, use_cache, cache_missed].
-behaviour(mod_last).
%% API
--export([init/2, import/2, get_last/2, store_last_info/4, remove_user/2]).
+-export([init/2, import/2, get_last/2, store_last_info/4,
+ remove_user/2, use_cache/1]).
-export([need_transform/1, transform/1]).
-include("mod_last.hrl").
%%%===================================================================
init(_Host, _Opts) ->
ejabberd_mnesia:create(?MODULE, last_activity,
- [{disc_copies, [node()]},
+ [{disc_only_copies, [node()]},
{attributes, record_info(fields, last_activity)}]).
+use_cache(Host) ->
+ case mnesia:table_info(last_activity, storage_type) of
+ disc_only_copies ->
+ gen_mod:get_module_opt(
+ Host, mod_last, use_cache,
+ ejabberd_config:use_cache(Host));
+ _ ->
+ false
+ end.
+
get_last(LUser, LServer) ->
case mnesia:dirty_read(last_activity, {LUser, LServer}) of
[] ->
- not_found;
+ error;
[#last_activity{timestamp = TimeStamp,
status = Status}] ->
- {ok, TimeStamp, Status}
+ {ok, {TimeStamp, Status}}
end.
store_last_info(LUser, LServer, TimeStamp, Status) ->
- US = {LUser, LServer},
- F = fun () ->
- mnesia:write(#last_activity{us = US,
- timestamp = TimeStamp,
- status = Status})
- end,
- mnesia:transaction(F).
+ mnesia:dirty_write(#last_activity{us = {LUser, LServer},
+ timestamp = TimeStamp,
+ status = Status}).
remove_user(LUser, LServer) ->
US = {LUser, LServer},
- F = fun () -> mnesia:delete({last_activity, US}) end,
- mnesia:transaction(F).
+ mnesia:dirty_delete({last_activity, US}).
import(_LServer, #last_activity{} = LA) ->
mnesia:dirty_write(LA).