2017-07-20 20:22:50 +02:00
|
|
|
%%%----------------------------------------------------------------------
|
|
|
|
%%% File : mod_push_mnesia.erl
|
|
|
|
%%% Author : Holger Weiss <holger@zedat.fu-berlin.de>
|
|
|
|
%%% Purpose : Mnesia backend for Push Notifications (XEP-0357)
|
|
|
|
%%% Created : 15 Jul 2017 by Holger Weiss <holger@zedat.fu-berlin.de>
|
|
|
|
%%%
|
|
|
|
%%%
|
2022-02-10 17:21:43 +01:00
|
|
|
%%% ejabberd, Copyright (C) 2017-2022 ProcessOne
|
2017-07-20 20:22:50 +02:00
|
|
|
%%%
|
|
|
|
%%% 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_push_mnesia).
|
|
|
|
-author('holger@zedat.fu-berlin.de').
|
|
|
|
|
2018-01-31 14:57:43 +01:00
|
|
|
-behaviour(mod_push).
|
2017-07-20 20:22:50 +02:00
|
|
|
|
|
|
|
%% API
|
|
|
|
-export([init/2, store_session/6, lookup_session/4, lookup_session/3,
|
|
|
|
lookup_sessions/3, lookup_sessions/2, lookup_sessions/1,
|
2017-10-27 10:46:37 +02:00
|
|
|
delete_session/3, delete_old_sessions/2, transform/1]).
|
2017-07-20 20:22:50 +02:00
|
|
|
|
|
|
|
-include_lib("stdlib/include/ms_transform.hrl").
|
|
|
|
-include("logger.hrl").
|
2020-09-03 13:45:57 +02:00
|
|
|
-include_lib("xmpp/include/xmpp.hrl").
|
2017-10-26 20:05:09 +02:00
|
|
|
-include("mod_push.hrl").
|
2017-07-20 20:22:50 +02:00
|
|
|
|
|
|
|
%%%-------------------------------------------------------------------
|
|
|
|
%%% API
|
|
|
|
%%%-------------------------------------------------------------------
|
|
|
|
init(_Host, _Opts) ->
|
|
|
|
ejabberd_mnesia:create(?MODULE, push_session,
|
|
|
|
[{disc_only_copies, [node()]},
|
|
|
|
{type, bag},
|
|
|
|
{attributes, record_info(fields, push_session)}]).
|
|
|
|
|
|
|
|
store_session(LUser, LServer, TS, PushJID, Node, XData) ->
|
|
|
|
US = {LUser, LServer},
|
|
|
|
PushLJID = jid:tolower(PushJID),
|
|
|
|
MaxSessions = ejabberd_sm:get_max_user_sessions(LUser, LServer),
|
|
|
|
F = fun() ->
|
2018-05-23 21:40:54 +02:00
|
|
|
enforce_max_sessions(US, MaxSessions),
|
2017-07-20 20:22:50 +02:00
|
|
|
mnesia:write(#push_session{us = US,
|
|
|
|
timestamp = TS,
|
|
|
|
service = PushLJID,
|
|
|
|
node = Node,
|
2017-10-27 10:46:37 +02:00
|
|
|
xml = encode_xdata(XData)})
|
2017-07-20 20:22:50 +02:00
|
|
|
end,
|
|
|
|
case mnesia:transaction(F) of
|
|
|
|
{atomic, ok} ->
|
|
|
|
{ok, {TS, PushLJID, Node, XData}};
|
|
|
|
{aborted, E} ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?ERROR_MSG("Cannot store push session for ~ts@~ts: ~p",
|
2017-07-20 20:22:50 +02:00
|
|
|
[LUser, LServer, E]),
|
2017-10-26 19:11:43 +02:00
|
|
|
{error, db_failure}
|
2017-07-20 20:22:50 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
lookup_session(LUser, LServer, PushJID, Node) ->
|
|
|
|
PushLJID = jid:tolower(PushJID),
|
|
|
|
MatchSpec = ets:fun2ms(
|
|
|
|
fun(#push_session{us = {U, S}, service = P, node = N} = Rec)
|
|
|
|
when U == LUser,
|
|
|
|
S == LServer,
|
|
|
|
P == PushLJID,
|
|
|
|
N == Node ->
|
|
|
|
Rec
|
|
|
|
end),
|
|
|
|
case mnesia:dirty_select(push_session, MatchSpec) of
|
2017-10-27 10:46:37 +02:00
|
|
|
[#push_session{timestamp = TS, xml = El}] ->
|
|
|
|
{ok, {TS, PushLJID, Node, decode_xdata(El)}};
|
2017-10-26 19:11:43 +02:00
|
|
|
[] ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?DEBUG("No push session found for ~ts@~ts (~p, ~ts)",
|
2017-07-20 20:22:50 +02:00
|
|
|
[LUser, LServer, PushJID, Node]),
|
2017-10-27 09:55:48 +02:00
|
|
|
{error, notfound}
|
2017-07-20 20:22:50 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
lookup_session(LUser, LServer, TS) ->
|
|
|
|
MatchSpec = ets:fun2ms(
|
|
|
|
fun(#push_session{us = {U, S}, timestamp = T} = Rec)
|
|
|
|
when U == LUser,
|
|
|
|
S == LServer,
|
|
|
|
T == TS ->
|
|
|
|
Rec
|
|
|
|
end),
|
|
|
|
case mnesia:dirty_select(push_session, MatchSpec) of
|
2017-10-27 10:46:37 +02:00
|
|
|
[#push_session{service = PushLJID, node = Node, xml = El}] ->
|
|
|
|
{ok, {TS, PushLJID, Node, decode_xdata(El)}};
|
2017-10-26 19:11:43 +02:00
|
|
|
[] ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?DEBUG("No push session found for ~ts@~ts (~p)",
|
2017-07-20 20:22:50 +02:00
|
|
|
[LUser, LServer, TS]),
|
2017-10-27 09:55:48 +02:00
|
|
|
{error, notfound}
|
2017-07-20 20:22:50 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
lookup_sessions(LUser, LServer, PushJID) ->
|
|
|
|
PushLJID = jid:tolower(PushJID),
|
|
|
|
MatchSpec = ets:fun2ms(
|
2018-06-18 23:12:27 +02:00
|
|
|
fun(#push_session{us = {U, S}, service = P} = Rec)
|
2017-07-20 20:22:50 +02:00
|
|
|
when U == LUser,
|
|
|
|
S == LServer,
|
|
|
|
P == PushLJID ->
|
2017-10-27 10:46:37 +02:00
|
|
|
Rec
|
2017-07-20 20:22:50 +02:00
|
|
|
end),
|
2017-10-27 10:46:37 +02:00
|
|
|
Records = mnesia:dirty_select(push_session, MatchSpec),
|
|
|
|
{ok, records_to_sessions(Records)}.
|
2017-07-20 20:22:50 +02:00
|
|
|
|
|
|
|
lookup_sessions(LUser, LServer) ->
|
|
|
|
Records = mnesia:dirty_read(push_session, {LUser, LServer}),
|
2017-10-27 10:46:37 +02:00
|
|
|
{ok, records_to_sessions(Records)}.
|
2017-07-20 20:22:50 +02:00
|
|
|
|
|
|
|
lookup_sessions(LServer) ->
|
|
|
|
MatchSpec = ets:fun2ms(
|
2018-06-18 23:12:27 +02:00
|
|
|
fun(#push_session{us = {_U, S}} = Rec)
|
2017-07-20 20:22:50 +02:00
|
|
|
when S == LServer ->
|
2018-06-18 23:05:08 +02:00
|
|
|
Rec
|
2017-07-20 20:22:50 +02:00
|
|
|
end),
|
2017-10-27 10:46:37 +02:00
|
|
|
Records = mnesia:dirty_select(push_session, MatchSpec),
|
|
|
|
{ok, records_to_sessions(Records)}.
|
2017-07-20 20:22:50 +02:00
|
|
|
|
|
|
|
delete_session(LUser, LServer, TS) ->
|
|
|
|
MatchSpec = ets:fun2ms(
|
|
|
|
fun(#push_session{us = {U, S}, timestamp = T} = Rec)
|
|
|
|
when U == LUser,
|
|
|
|
S == LServer,
|
|
|
|
T == TS ->
|
|
|
|
Rec
|
|
|
|
end),
|
|
|
|
F = fun() ->
|
|
|
|
Recs = mnesia:select(push_session, MatchSpec),
|
|
|
|
lists:foreach(fun mnesia:delete_object/1, Recs)
|
|
|
|
end,
|
|
|
|
case mnesia:transaction(F) of
|
|
|
|
{atomic, ok} ->
|
|
|
|
ok;
|
|
|
|
{aborted, E} ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?ERROR_MSG("Cannot delete push session of ~ts@~ts: ~p",
|
2017-07-20 20:22:50 +02:00
|
|
|
[LUser, LServer, E]),
|
2017-10-26 19:11:43 +02:00
|
|
|
{error, db_failure}
|
2017-07-20 20:22:50 +02:00
|
|
|
end.
|
|
|
|
|
|
|
|
delete_old_sessions(_LServer, Time) ->
|
|
|
|
DelIfOld = fun(#push_session{timestamp = T} = Rec, ok) when T < Time ->
|
|
|
|
mnesia:delete_object(Rec);
|
|
|
|
(_Rec, ok) ->
|
|
|
|
ok
|
|
|
|
end,
|
|
|
|
F = fun() ->
|
|
|
|
mnesia:foldl(DelIfOld, ok, push_session)
|
|
|
|
end,
|
|
|
|
case mnesia:transaction(F) of
|
|
|
|
{atomic, ok} ->
|
|
|
|
ok;
|
|
|
|
{aborted, E} ->
|
|
|
|
?ERROR_MSG("Cannot delete old push sessions: ~p", [E]),
|
2017-10-26 19:11:43 +02:00
|
|
|
{error, db_failure}
|
2017-07-20 20:22:50 +02:00
|
|
|
end.
|
|
|
|
|
2017-10-27 10:46:37 +02:00
|
|
|
transform({push_session, US, TS, Service, Node, XData}) ->
|
|
|
|
?INFO_MSG("Transforming push_session Mnesia table", []),
|
|
|
|
#push_session{us = US, timestamp = TS, service = Service,
|
|
|
|
node = Node, xml = encode_xdata(XData)}.
|
|
|
|
|
2017-07-20 20:22:50 +02:00
|
|
|
%%--------------------------------------------------------------------
|
|
|
|
%% Internal functions.
|
|
|
|
%%--------------------------------------------------------------------
|
2018-05-23 21:40:54 +02:00
|
|
|
-spec enforce_max_sessions({binary(), binary()}, non_neg_integer() | infinity)
|
|
|
|
-> ok.
|
|
|
|
enforce_max_sessions(_US, infinity) ->
|
|
|
|
ok;
|
|
|
|
enforce_max_sessions({U, S} = US, MaxSessions) ->
|
|
|
|
case mnesia:wread({push_session, US}) of
|
|
|
|
Recs when length(Recs) >= MaxSessions ->
|
|
|
|
Recs1 = lists:sort(fun(#push_session{timestamp = TS1},
|
|
|
|
#push_session{timestamp = TS2}) ->
|
|
|
|
TS1 >= TS2
|
|
|
|
end, Recs),
|
|
|
|
OldRecs = lists:nthtail(MaxSessions - 1, Recs1),
|
2019-09-23 14:17:20 +02:00
|
|
|
?INFO_MSG("Disabling old push session(s) of ~ts@~ts", [U, S]),
|
2017-07-20 20:22:50 +02:00
|
|
|
lists:foreach(fun(Rec) -> mnesia:delete_object(Rec) end, OldRecs);
|
2018-05-23 21:40:54 +02:00
|
|
|
_ ->
|
2017-07-20 20:22:50 +02:00
|
|
|
ok
|
|
|
|
end.
|
2017-10-27 10:46:37 +02:00
|
|
|
|
|
|
|
decode_xdata(undefined) ->
|
|
|
|
undefined;
|
|
|
|
decode_xdata(El) ->
|
|
|
|
xmpp:decode(El).
|
|
|
|
|
|
|
|
encode_xdata(undefined) ->
|
|
|
|
undefined;
|
|
|
|
encode_xdata(XData) ->
|
|
|
|
xmpp:encode(XData).
|
|
|
|
|
|
|
|
records_to_sessions(Records) ->
|
|
|
|
[{TS, PushLJID, Node, decode_xdata(El)}
|
|
|
|
|| #push_session{timestamp = TS,
|
|
|
|
service = PushLJID,
|
|
|
|
node = Node,
|
|
|
|
xml = El} <- Records].
|