aboutsummaryrefslogtreecommitdiff
path: root/src/mod_push_mnesia.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_push_mnesia.erl')
-rw-r--r--src/mod_push_mnesia.erl211
1 files changed, 211 insertions, 0 deletions
diff --git a/src/mod_push_mnesia.erl b/src/mod_push_mnesia.erl
new file mode 100644
index 000000000..1459446ba
--- /dev/null
+++ b/src/mod_push_mnesia.erl
@@ -0,0 +1,211 @@
+%%%----------------------------------------------------------------------
+%%% 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>
+%%%
+%%%
+%%% ejabberd, Copyright (C) 2017-2019 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_push_mnesia).
+-author('holger@zedat.fu-berlin.de').
+
+-behaviour(mod_push).
+
+%% API
+-export([init/2, store_session/6, lookup_session/4, lookup_session/3,
+ lookup_sessions/3, lookup_sessions/2, lookup_sessions/1,
+ delete_session/3, delete_old_sessions/2, transform/1]).
+
+-include_lib("stdlib/include/ms_transform.hrl").
+-include("logger.hrl").
+-include("xmpp.hrl").
+-include("mod_push.hrl").
+
+%%%-------------------------------------------------------------------
+%%% 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() ->
+ enforce_max_sessions(US, MaxSessions),
+ mnesia:write(#push_session{us = US,
+ timestamp = TS,
+ service = PushLJID,
+ node = Node,
+ xml = encode_xdata(XData)})
+ end,
+ case mnesia:transaction(F) of
+ {atomic, ok} ->
+ {ok, {TS, PushLJID, Node, XData}};
+ {aborted, E} ->
+ ?ERROR_MSG("Cannot store push session for ~ts@~ts: ~p",
+ [LUser, LServer, E]),
+ {error, db_failure}
+ 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
+ [#push_session{timestamp = TS, xml = El}] ->
+ {ok, {TS, PushLJID, Node, decode_xdata(El)}};
+ [] ->
+ ?DEBUG("No push session found for ~ts@~ts (~p, ~ts)",
+ [LUser, LServer, PushJID, Node]),
+ {error, notfound}
+ 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
+ [#push_session{service = PushLJID, node = Node, xml = El}] ->
+ {ok, {TS, PushLJID, Node, decode_xdata(El)}};
+ [] ->
+ ?DEBUG("No push session found for ~ts@~ts (~p)",
+ [LUser, LServer, TS]),
+ {error, notfound}
+ end.
+
+lookup_sessions(LUser, LServer, PushJID) ->
+ PushLJID = jid:tolower(PushJID),
+ MatchSpec = ets:fun2ms(
+ fun(#push_session{us = {U, S}, service = P} = Rec)
+ when U == LUser,
+ S == LServer,
+ P == PushLJID ->
+ Rec
+ end),
+ Records = mnesia:dirty_select(push_session, MatchSpec),
+ {ok, records_to_sessions(Records)}.
+
+lookup_sessions(LUser, LServer) ->
+ Records = mnesia:dirty_read(push_session, {LUser, LServer}),
+ {ok, records_to_sessions(Records)}.
+
+lookup_sessions(LServer) ->
+ MatchSpec = ets:fun2ms(
+ fun(#push_session{us = {_U, S}} = Rec)
+ when S == LServer ->
+ Rec
+ end),
+ Records = mnesia:dirty_select(push_session, MatchSpec),
+ {ok, records_to_sessions(Records)}.
+
+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} ->
+ ?ERROR_MSG("Cannot delete push session of ~ts@~ts: ~p",
+ [LUser, LServer, E]),
+ {error, db_failure}
+ 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]),
+ {error, db_failure}
+ end.
+
+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)}.
+
+%%--------------------------------------------------------------------
+%% Internal functions.
+%%--------------------------------------------------------------------
+-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),
+ ?INFO_MSG("Disabling old push session(s) of ~ts@~ts", [U, S]),
+ lists:foreach(fun(Rec) -> mnesia:delete_object(Rec) end, OldRecs);
+ _ ->
+ ok
+ end.
+
+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].