./ct_report/coverage/mod_last.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_last.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : jabber:iq:last support (XEP-0012)
5 %%% Created : 24 Oct 2003 by Alexey Shchepin <alexey@process-one.net>
6 %%%
7 %%%
8 %%% ejabberd, Copyright (C) 2002-2014 ProcessOne
9 %%%
10 %%% This program is free software; you can redistribute it and/or
11 %%% modify it under the terms of the GNU General Public License as
12 %%% published by the Free Software Foundation; either version 2 of the
13 %%% License, or (at your option) any later version.
14 %%%
15 %%% This program is distributed in the hope that it will be useful,
16 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
17 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 %%% General Public License for more details.
19 %%%
20 %%% You should have received a copy of the GNU General Public License along
21 %%% with this program; if not, write to the Free Software Foundation, Inc.,
22 %%% 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 %%%
24 %%%----------------------------------------------------------------------
25
26 -module(mod_last).
27
28 -author('alexey@process-one.net').
29
30 -xep([{xep, 12}, {version, "2.0"}]).
31
32 -behaviour(gen_mod).
33 -behaviour(mongoose_module_metrics).
34
35 %% Gen_mod callbacks
36 -export([start/2,
37 stop/1,
38 config_spec/0,
39 supported_features/0]).
40
41 %% IQ and hook handlers
42 -export([process_local_iq/5,
43 process_sm_iq/5,
44 remove_user/3,
45 on_presence_update/5,
46 session_cleanup/5,
47 remove_domain/3]).
48
49 %% API
50 -export([store_last_info/5,
51 get_last_info/3,
52 count_active_users/3]).
53
54 -export([config_metrics/1]).
55
56 -ignore_xref([
57 behaviour_info/1, on_presence_update/5, process_local_iq/4,
58 process_sm_iq/4, remove_user/3, session_cleanup/5, remove_domain/3
59 ]).
60
61 -include("mongoose.hrl").
62 -include("mongoose_config_spec.hrl").
63
64 -include("jlib.hrl").
65
66 %% ------------------------------------------------------------------
67 %% Backend callbacks
68
69 -export_type([timestamp/0, status/0]).
70
71 -type timestamp() :: non_neg_integer().
72 -type status() :: binary().
73
74 -spec start(mongooseim:host_type(), list()) -> 'ok'.
75 start(HostType, Opts) ->
76 11 IQDisc = gen_mod:get_opt(iqdisc, Opts, one_queue),
77
78 11 mod_last_backend:init(HostType, Opts),
79
80 11 [gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_LAST, Component, Fn, #{}, IQDisc) ||
81 11 {Component, Fn} <- iq_handlers()],
82 11 ejabberd_hooks:add(hooks(HostType)).
83
84 -spec stop(mongooseim:host_type()) -> ok.
85 stop(HostType) ->
86 11 ejabberd_hooks:delete(hooks(HostType)),
87 11 [gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_LAST, Component) ||
88 11 {Component, _Fn} <- iq_handlers()],
89 11 ok.
90
91 iq_handlers() ->
92 22 [{ejabberd_local, fun ?MODULE:process_local_iq/5},
93 {ejabberd_sm, fun ?MODULE:process_sm_iq/5}].
94
95 hooks(HostType) ->
96 22 [{remove_user, HostType, ?MODULE, remove_user, 50},
97 {anonymous_purge_hook, HostType, ?MODULE, remove_user, 50},
98 {unset_presence_hook, HostType, ?MODULE, on_presence_update, 50},
99 {session_cleanup, HostType, ?MODULE, session_cleanup, 50},
100 {remove_domain, HostType, ?MODULE, remove_domain, 50}].
101
102 %%%
103 %%% config_spec
104 %%%
105
106 -spec config_spec() -> mongoose_config_spec:config_section().
107 config_spec() ->
108 160 #section{
109 items = #{<<"iqdisc">> => mongoose_config_spec:iqdisc(),
110 <<"backend">> => #option{type = atom,
111 validate = {module, mod_last}},
112 <<"riak">> => riak_config_spec()
113 }
114 }.
115
116 riak_config_spec() ->
117 160 #section{items = #{<<"bucket_type">> => #option{type = binary,
118 validate = non_empty}
119 },
120 wrap = none
121 }.
122
123
:-(
supported_features() -> [dynamic_domains].
124
125 %%%
126 %%% Uptime of ejabberd node
127 %%%
128 -spec process_local_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map())
129 -> {mongoose_acc:t(), jlib:iq()}.
130 process_local_iq(Acc, _From, _To, #iq{type = Type, sub_el = SubEl} = IQ, _Extra) ->
131 1 case Type of
132 set ->
133
:-(
{Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}};
134 get ->
135 1 Sec = get_node_uptime(),
136 1 {Acc, IQ#iq{type = result,
137 sub_el =
138 [#xmlel{name = <<"query">>,
139 attrs =
140 [{<<"xmlns">>, ?NS_LAST},
141 {<<"seconds">>,
142 integer_to_binary(Sec)}],
143 children = []}]}}
144 end.
145
146 -spec get_node_uptime() -> non_neg_integer().
147 get_node_uptime() ->
148 1 case mongoose_config:lookup_opt(node_start) of
149 {ok, {node_start, Seconds}} ->
150
:-(
erlang:system_time(second) - Seconds;
151 {error, not_found} ->
152 1 trunc(element(1, erlang:statistics(wall_clock))/1000)
153 end.
154
155 %%%
156 %%% Serve queries about user last online
157 %%%
158 -spec process_sm_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) ->
159 {mongoose_acc:t(), jlib:iq()}.
160 process_sm_iq(Acc, _From, _To, #iq{type = set, sub_el = SubEl} = IQ, _Extra) ->
161
:-(
{Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}};
162 process_sm_iq(Acc, From, To, #iq{type = get, sub_el = SubEl} = IQ, _Extra) ->
163 4 HostType = mongoose_acc:host_type(Acc),
164 4 {Subscription, _Groups} = mongoose_hooks:roster_get_jid_info(HostType, To, From),
165 4 MutualSubscription = Subscription == both,
166 4 RequesterSubscribedToTarget = Subscription == from,
167 4 QueryingSameUsersLast = (From#jid.luser == To#jid.luser) and
168 (From#jid.lserver == To#jid.lserver),
169 4 case MutualSubscription or RequesterSubscribedToTarget or QueryingSameUsersLast of
170 true ->
171 3 UserListRecord = mongoose_hooks:privacy_get_user_list(HostType, To),
172 3 {Acc1, Res} = mongoose_privacy:privacy_check_packet(Acc, To,
173 UserListRecord, To, From,
174 out),
175 3 {Acc1, make_response(HostType, IQ, SubEl, To, Res)};
176 false ->
177 1 {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:forbidden()]}}
178 end.
179
180 -spec make_response(mongooseim:host_type(), jlib:iq(), SubEl :: 'undefined' | [exml:element()],
181 jid:jid(), allow | deny) -> jlib:iq().
182 make_response(_HostType, IQ, SubEl, _, deny) ->
183
:-(
IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:forbidden()]};
184 make_response(HostType, IQ, SubEl, JID, allow) ->
185 3 #jid{luser = LUser, lserver = LServer} = JID,
186 3 case ejabberd_sm:get_user_resources(JID) of
187 [] ->
188 2 case get_last(HostType, LUser, LServer) of
189 {error, _Reason} ->
190
:-(
IQ#iq{type = error,
191 sub_el = [SubEl, mongoose_xmpp_errors:internal_server_error()]};
192 not_found ->
193
:-(
IQ#iq{type = error,
194 sub_el = [SubEl, mongoose_xmpp_errors:service_unavailable()]};
195 {ok, TimeStamp, Status} ->
196 2 TimeStamp2 = erlang:system_time(second),
197 2 Sec = TimeStamp2 - TimeStamp,
198 2 IQ#iq{type = result,
199 sub_el =
200 [#xmlel{name = <<"query">>,
201 attrs =
202 [{<<"xmlns">>, ?NS_LAST},
203 {<<"seconds">>,
204 integer_to_binary(Sec)}],
205 children = [{xmlcdata, Status}]}]}
206 end;
207 _ ->
208 1 IQ#iq{type = result,
209 sub_el =
210 [#xmlel{name = <<"query">>,
211 attrs =
212 [{<<"xmlns">>, ?NS_LAST},
213 {<<"seconds">>, <<"0">>}],
214 children = []}]}
215 end.
216
217 -spec get_last_info(mongooseim:host_type(), jid:luser(), jid:lserver())
218 -> 'not_found' | {'ok', integer(), binary()}.
219 get_last_info(HostType, LUser, LServer) ->
220
:-(
case get_last(HostType, LUser, LServer) of
221
:-(
{error, _Reason} -> not_found;
222
:-(
Res -> Res
223 end.
224
225 -spec remove_user(mongoose_acc:t(), jid:user(), jid:server()) -> mongoose_acc:t().
226 remove_user(Acc, User, Server) ->
227 25 HostType = mongoose_acc:host_type(Acc),
228 25 LUser = jid:nodeprep(User),
229 25 LServer = jid:nameprep(Server),
230 25 R = mod_last_backend:remove_user(HostType, LUser, LServer),
231 25 mongoose_lib:log_if_backend_error(R, ?MODULE, ?LINE, {Acc, User, Server}),
232 25 Acc.
233
234 -spec remove_domain(mongoose_hooks:simple_acc(), mongooseim:host_type(), jid:lserver()) -> mongoose_hooks:simple_acc().
235 remove_domain(Acc, HostType, Domain) ->
236
:-(
mod_last_backend:remove_domain(HostType, Domain),
237
:-(
Acc.
238
239 -spec on_presence_update(mongoose_acc:t(), jid:luser(), jid:lserver(), jid:lresource(), status()) ->
240 mongoose_acc:t().
241 on_presence_update(Acc, LUser, LServer, _Resource, Status) ->
242 64 store_last_info(Acc, LUser, LServer, Status).
243
244 -spec session_cleanup(mongoose_acc:t(), jid:luser(), jid:lserver(), jid:lresource(),
245 ejabberd_sm:sid()) ->
246 mongoose_acc:t().
247 session_cleanup(Acc, LUser, LServer, _LResource, _SID) ->
248
:-(
store_last_info(Acc, LUser, LServer, <<>>).
249
250 -spec store_last_info(mongoose_acc:t(), jid:luser(), jid:lserver(), status()) -> mongoose_acc:t().
251 store_last_info(Acc, LUser, LServer, Status) ->
252 64 HostType = mongoose_acc:host_type(Acc),
253 64 TimeStamp = erlang:system_time(second),
254 64 store_last_info(HostType, LUser, LServer, TimeStamp, Status),
255 64 Acc.
256
257 -spec store_last_info(mongooseim:host_type(), jid:luser(), jid:lserver(), timestamp(), status()) -> ok.
258 store_last_info(HostType, LUser, LServer, TimeStamp, Status) ->
259 68 case mod_last_backend:set_last_info(HostType, LUser, LServer, TimeStamp, Status) of
260 {error, Reason} ->
261
:-(
?LOG_ERROR(#{what => set_last_info_failed,
262 text => <<"Unexpected error while storing mod_last information">>,
263 user => LUser, server => LServer,
264 timestamp => TimeStamp, status => Status,
265
:-(
reason => Reason});
266 ok ->
267 68 ok
268 end.
269
270 -spec get_last(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
271 {ok, timestamp(), status()} | {error, term()} | not_found.
272 get_last(HostType, LUser, LServer) ->
273 2 mod_last_backend:get_last(HostType, LUser, LServer).
274
275 -spec count_active_users(mongooseim:host_type(), jid:lserver(), timestamp()) -> non_neg_integer().
276 count_active_users(HostType, LServer, Timestamp) ->
277 3 mod_last_backend:count_active_users(HostType, LServer, Timestamp).
278
279 config_metrics(Host) ->
280 6 OptsToReport = [{backend, mnesia}], %list of tuples {option, defualt_value}
281 6 mongoose_module_metrics:opts_for_module(Host, ?MODULE, OptsToReport).
Line Hits Source