2017-10-26 19:11:43 +02:00
|
|
|
%%%----------------------------------------------------------------------
|
2017-11-10 17:51:22 +01:00
|
|
|
%%% File : mod_push_sql.erl
|
|
|
|
%%% Author : Evgeniy Khramtsov <ekhramtsov@process-one.net>
|
|
|
|
%%% Purpose :
|
|
|
|
%%% Created : 26 Oct 2017 by Evgeny Khramtsov <ekhramtsov@process-one.net>
|
|
|
|
%%%
|
|
|
|
%%%
|
2024-01-22 16:40:01 +01:00
|
|
|
%%% ejabberd, Copyright (C) 2017-2024 ProcessOne
|
2017-10-26 19:11:43 +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.
|
|
|
|
%%%
|
|
|
|
%%%----------------------------------------------------------------------
|
2017-11-10 17:51:22 +01:00
|
|
|
|
2017-10-26 19:11:43 +02:00
|
|
|
-module(mod_push_sql).
|
|
|
|
-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,
|
2017-10-26 20:05:09 +02:00
|
|
|
delete_session/3, delete_old_sessions/2, export/1]).
|
2024-01-24 20:49:59 +01:00
|
|
|
-export([sql_schemas/0]).
|
2017-10-26 19:11:43 +02:00
|
|
|
|
2020-09-03 13:45:57 +02:00
|
|
|
-include_lib("xmpp/include/xmpp.hrl").
|
2017-10-26 19:11:43 +02:00
|
|
|
-include("logger.hrl").
|
|
|
|
-include("ejabberd_sql_pt.hrl").
|
2017-10-26 20:05:09 +02:00
|
|
|
-include("mod_push.hrl").
|
2017-10-26 19:11:43 +02:00
|
|
|
|
|
|
|
%%%===================================================================
|
|
|
|
%%% API
|
|
|
|
%%%===================================================================
|
2023-09-28 02:37:36 +02:00
|
|
|
init(Host, _Opts) ->
|
2024-01-24 20:49:59 +01:00
|
|
|
ejabberd_sql_schema:update_schema(Host, ?MODULE, sql_schemas()),
|
2017-10-26 19:11:43 +02:00
|
|
|
ok.
|
|
|
|
|
2024-01-24 20:49:59 +01:00
|
|
|
sql_schemas() ->
|
2023-09-28 02:37:36 +02:00
|
|
|
[#sql_schema{
|
|
|
|
version = 1,
|
|
|
|
tables =
|
|
|
|
[#sql_table{
|
|
|
|
name = <<"push_session">>,
|
|
|
|
columns =
|
|
|
|
[#sql_column{name = <<"username">>, type = text},
|
|
|
|
#sql_column{name = <<"server_host">>, type = text},
|
|
|
|
#sql_column{name = <<"timestamp">>, type = bigint},
|
|
|
|
#sql_column{name = <<"service">>, type = text},
|
|
|
|
#sql_column{name = <<"node">>, type = text},
|
|
|
|
#sql_column{name = <<"xml">>, type = text}],
|
|
|
|
indices = [#sql_index{
|
|
|
|
columns = [<<"server_host">>, <<"username">>,
|
|
|
|
<<"timestamp">>],
|
|
|
|
unique = true},
|
|
|
|
#sql_index{
|
|
|
|
columns = [<<"server_host">>, <<"username">>,
|
|
|
|
<<"service">>, <<"node">>],
|
|
|
|
unique = true}]}]}].
|
|
|
|
|
2017-10-26 19:11:43 +02:00
|
|
|
store_session(LUser, LServer, NowTS, PushJID, Node, XData) ->
|
2017-10-26 20:05:09 +02:00
|
|
|
XML = encode_xdata(XData),
|
2017-10-26 19:11:43 +02:00
|
|
|
TS = misc:now_to_usec(NowTS),
|
|
|
|
PushLJID = jid:tolower(PushJID),
|
|
|
|
Service = jid:encode(PushLJID),
|
2018-05-23 20:02:52 +02:00
|
|
|
MaxSessions = ejabberd_sm:get_max_user_sessions(LUser, LServer),
|
|
|
|
enforce_max_sessions(LUser, LServer, MaxSessions),
|
2017-10-26 19:11:43 +02:00
|
|
|
case ?SQL_UPSERT(LServer, "push_session",
|
|
|
|
["!username=%(LUser)s",
|
2017-11-02 15:03:30 +01:00
|
|
|
"!server_host=%(LServer)s",
|
2021-09-19 05:02:06 +02:00
|
|
|
"timestamp=%(TS)d",
|
2017-10-26 19:11:43 +02:00
|
|
|
"!service=%(Service)s",
|
|
|
|
"!node=%(Node)s",
|
|
|
|
"xml=%(XML)s"]) of
|
|
|
|
ok ->
|
|
|
|
{ok, {NowTS, PushLJID, Node, XData}};
|
2017-12-17 17:46:55 +01:00
|
|
|
_Err ->
|
2017-10-26 19:11:43 +02:00
|
|
|
{error, db_failure}
|
|
|
|
end.
|
|
|
|
|
|
|
|
lookup_session(LUser, LServer, PushJID, Node) ->
|
|
|
|
PushLJID = jid:tolower(PushJID),
|
|
|
|
Service = jid:encode(PushLJID),
|
|
|
|
case ejabberd_sql:sql_query(
|
|
|
|
LServer,
|
|
|
|
?SQL("select @(timestamp)d, @(xml)s from push_session "
|
2017-11-02 15:03:30 +01:00
|
|
|
"where username=%(LUser)s and %(LServer)H "
|
|
|
|
"and service=%(Service)s "
|
2017-10-26 19:11:43 +02:00
|
|
|
"and node=%(Node)s")) of
|
|
|
|
{selected, [{TS, XML}]} ->
|
|
|
|
NowTS = misc:usec_to_now(TS),
|
|
|
|
XData = decode_xdata(XML, LUser, LServer),
|
|
|
|
{ok, {NowTS, PushLJID, Node, XData}};
|
|
|
|
{selected, []} ->
|
2017-10-27 09:55:48 +02:00
|
|
|
{error, notfound};
|
2017-12-17 17:46:55 +01:00
|
|
|
_Err ->
|
2017-10-26 19:11:43 +02:00
|
|
|
{error, db_failure}
|
|
|
|
end.
|
|
|
|
|
|
|
|
lookup_session(LUser, LServer, NowTS) ->
|
|
|
|
TS = misc:now_to_usec(NowTS),
|
|
|
|
case ejabberd_sql:sql_query(
|
|
|
|
LServer,
|
|
|
|
?SQL("select @(service)s, @(node)s, @(xml)s "
|
2017-11-02 15:03:30 +01:00
|
|
|
"from push_session where username=%(LUser)s and %(LServer)H "
|
2017-10-26 19:11:43 +02:00
|
|
|
"and timestamp=%(TS)d")) of
|
|
|
|
{selected, [{Service, Node, XML}]} ->
|
|
|
|
PushLJID = jid:tolower(jid:decode(Service)),
|
|
|
|
XData = decode_xdata(XML, LUser, LServer),
|
|
|
|
{ok, {NowTS, PushLJID, Node, XData}};
|
|
|
|
{selected, []} ->
|
2017-10-27 09:55:48 +02:00
|
|
|
{error, notfound};
|
2017-12-17 17:46:55 +01:00
|
|
|
_Err ->
|
2017-10-26 19:11:43 +02:00
|
|
|
{error, db_failure}
|
|
|
|
end.
|
|
|
|
|
|
|
|
lookup_sessions(LUser, LServer, PushJID) ->
|
|
|
|
PushLJID = jid:tolower(PushJID),
|
|
|
|
Service = jid:encode(PushLJID),
|
|
|
|
case ejabberd_sql:sql_query(
|
|
|
|
LServer,
|
|
|
|
?SQL("select @(timestamp)d, @(xml)s, @(node)s from push_session "
|
2017-11-02 15:03:30 +01:00
|
|
|
"where username=%(LUser)s and %(LServer)H "
|
|
|
|
"and service=%(Service)s")) of
|
2017-10-26 19:11:43 +02:00
|
|
|
{selected, Rows} ->
|
|
|
|
{ok, lists:map(
|
|
|
|
fun({TS, XML, Node}) ->
|
|
|
|
NowTS = misc:usec_to_now(TS),
|
|
|
|
XData = decode_xdata(XML, LUser, LServer),
|
|
|
|
{NowTS, PushLJID, Node, XData}
|
|
|
|
end, Rows)};
|
2017-12-17 17:46:55 +01:00
|
|
|
_Err ->
|
2017-10-26 19:11:43 +02:00
|
|
|
{error, db_failure}
|
|
|
|
end.
|
|
|
|
|
|
|
|
lookup_sessions(LUser, LServer) ->
|
|
|
|
case ejabberd_sql:sql_query(
|
|
|
|
LServer,
|
|
|
|
?SQL("select @(timestamp)d, @(xml)s, @(node)s, @(service)s "
|
2017-11-02 15:03:30 +01:00
|
|
|
"from push_session "
|
|
|
|
"where username=%(LUser)s and %(LServer)H")) of
|
2017-10-26 19:11:43 +02:00
|
|
|
{selected, Rows} ->
|
|
|
|
{ok, lists:map(
|
|
|
|
fun({TS, XML, Node, Service}) ->
|
|
|
|
NowTS = misc:usec_to_now(TS),
|
|
|
|
XData = decode_xdata(XML, LUser, LServer),
|
|
|
|
PushLJID = jid:tolower(jid:decode(Service)),
|
|
|
|
{NowTS, PushLJID,Node, XData}
|
|
|
|
end, Rows)};
|
2017-12-17 17:46:55 +01:00
|
|
|
_Err ->
|
2017-10-26 19:11:43 +02:00
|
|
|
{error, db_failure}
|
|
|
|
end.
|
|
|
|
|
|
|
|
lookup_sessions(LServer) ->
|
|
|
|
case ejabberd_sql:sql_query(
|
|
|
|
LServer,
|
|
|
|
?SQL("select @(username)s, @(timestamp)d, @(xml)s, "
|
2017-11-02 15:03:30 +01:00
|
|
|
"@(node)s, @(service)s from push_session "
|
|
|
|
"where %(LServer)H")) of
|
2017-10-26 19:11:43 +02:00
|
|
|
{selected, Rows} ->
|
|
|
|
{ok, lists:map(
|
|
|
|
fun({LUser, TS, XML, Node, Service}) ->
|
|
|
|
NowTS = misc:usec_to_now(TS),
|
|
|
|
XData = decode_xdata(XML, LUser, LServer),
|
|
|
|
PushLJID = jid:tolower(jid:decode(Service)),
|
|
|
|
{NowTS, PushLJID, Node, XData}
|
|
|
|
end, Rows)};
|
2017-12-17 17:46:55 +01:00
|
|
|
_Err ->
|
2017-10-26 19:11:43 +02:00
|
|
|
{error, db_failure}
|
|
|
|
end.
|
|
|
|
|
|
|
|
delete_session(LUser, LServer, NowTS) ->
|
|
|
|
TS = misc:now_to_usec(NowTS),
|
|
|
|
case ejabberd_sql:sql_query(
|
|
|
|
LServer,
|
|
|
|
?SQL("delete from push_session where "
|
2017-11-02 15:03:30 +01:00
|
|
|
"username=%(LUser)s and %(LServer)H and timestamp=%(TS)d")) of
|
2017-10-26 19:11:43 +02:00
|
|
|
{updated, _} ->
|
|
|
|
ok;
|
2017-12-17 17:46:55 +01:00
|
|
|
_Err ->
|
2017-10-26 19:11:43 +02:00
|
|
|
{error, db_failure}
|
|
|
|
end.
|
|
|
|
|
|
|
|
delete_old_sessions(LServer, Time) ->
|
|
|
|
TS = misc:now_to_usec(Time),
|
|
|
|
case ejabberd_sql:sql_query(
|
|
|
|
LServer,
|
2017-11-02 15:03:30 +01:00
|
|
|
?SQL("delete from push_session where timestamp<%(TS)d "
|
|
|
|
"and %(LServer)H")) of
|
2017-10-26 19:11:43 +02:00
|
|
|
{updated, _} ->
|
|
|
|
ok;
|
2017-12-17 17:46:55 +01:00
|
|
|
_Err ->
|
2017-10-26 19:11:43 +02:00
|
|
|
{error, db_failure}
|
|
|
|
end.
|
|
|
|
|
2017-10-26 20:05:09 +02:00
|
|
|
export(_Server) ->
|
|
|
|
[{push_session,
|
|
|
|
fun(Host, #push_session{us = {LUser, LServer},
|
|
|
|
timestamp = NowTS,
|
|
|
|
service = PushLJID,
|
|
|
|
node = Node,
|
2017-10-27 10:46:37 +02:00
|
|
|
xml = XData})
|
2017-10-26 20:05:09 +02:00
|
|
|
when LServer == Host ->
|
|
|
|
TS = misc:now_to_usec(NowTS),
|
|
|
|
Service = jid:encode(PushLJID),
|
|
|
|
XML = encode_xdata(XData),
|
|
|
|
[?SQL("delete from push_session where "
|
2017-11-02 15:03:30 +01:00
|
|
|
"username=%(LUser)s and %(LServer)H and "
|
|
|
|
"timestamp=%(TS)d and "
|
2017-10-26 20:05:09 +02:00
|
|
|
"service=%(Service)s and node=%(Node)s and "
|
|
|
|
"xml=%(XML)s;"),
|
2017-11-02 15:03:30 +01:00
|
|
|
?SQL_INSERT(
|
|
|
|
"push_session",
|
|
|
|
["username=%(LUser)s",
|
|
|
|
"server_host=%(LServer)s",
|
|
|
|
"timestamp=%(TS)d",
|
|
|
|
"service=%(Service)s",
|
|
|
|
"node=%(Node)s",
|
|
|
|
"xml=%(XML)s"])];
|
2017-10-26 20:05:09 +02:00
|
|
|
(_Host, _R) ->
|
|
|
|
[]
|
|
|
|
end}].
|
|
|
|
|
2017-10-26 19:11:43 +02:00
|
|
|
%%%===================================================================
|
|
|
|
%%% Internal functions
|
|
|
|
%%%===================================================================
|
2018-05-23 20:02:52 +02:00
|
|
|
enforce_max_sessions(_LUser, _LServer, infinity) ->
|
|
|
|
ok;
|
|
|
|
enforce_max_sessions(LUser, LServer, MaxSessions) ->
|
|
|
|
case lookup_sessions(LUser, LServer) of
|
|
|
|
{ok, Sessions} when length(Sessions) >= MaxSessions ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?INFO_MSG("Disabling old push session(s) of ~ts@~ts",
|
2018-05-23 20:02:52 +02:00
|
|
|
[LUser, LServer]),
|
|
|
|
Sessions1 = lists:sort(fun({TS1, _, _, _}, {TS2, _, _, _}) ->
|
|
|
|
TS1 >= TS2
|
|
|
|
end, Sessions),
|
|
|
|
OldSessions = lists:nthtail(MaxSessions - 1, Sessions1),
|
|
|
|
lists:foreach(fun({TS, _, _, _}) ->
|
|
|
|
delete_session(LUser, LServer, TS)
|
|
|
|
end, OldSessions);
|
|
|
|
_ ->
|
|
|
|
ok
|
|
|
|
end.
|
|
|
|
|
2017-10-26 19:11:43 +02:00
|
|
|
decode_xdata(<<>>, _LUser, _LServer) ->
|
|
|
|
undefined;
|
|
|
|
decode_xdata(XML, LUser, LServer) ->
|
|
|
|
case fxml_stream:parse_element(XML) of
|
|
|
|
#xmlel{} = El ->
|
|
|
|
try xmpp:decode(El)
|
|
|
|
catch _:{xmpp_codec, Why} ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?ERROR_MSG("Failed to decode ~ts for user ~ts@~ts "
|
|
|
|
"from table 'push_session': ~ts",
|
2017-10-26 19:11:43 +02:00
|
|
|
[XML, LUser, LServer, xmpp:format_error(Why)]),
|
|
|
|
undefined
|
|
|
|
end;
|
|
|
|
Err ->
|
2019-09-23 14:17:20 +02:00
|
|
|
?ERROR_MSG("Failed to decode ~ts for user ~ts@~ts from "
|
2017-10-26 19:11:43 +02:00
|
|
|
"table 'push_session': ~p",
|
|
|
|
[XML, LUser, LServer, Err]),
|
|
|
|
undefined
|
|
|
|
end.
|
2017-10-26 20:05:09 +02:00
|
|
|
|
|
|
|
encode_xdata(undefined) ->
|
|
|
|
<<>>;
|
|
|
|
encode_xdata(XData) ->
|
|
|
|
fxml:element_to_binary(xmpp:encode(XData)).
|