./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 remove_unused_backend_opts/1]).
49
50 %% API
51 -export([store_last_info/5,
52 get_last_info/3,
53 count_active_users/3]).
54
55 -export([config_metrics/1]).
56
57 -ignore_xref([
58 behaviour_info/1, on_presence_update/5, process_local_iq/4,
59 process_sm_iq/4, remove_user/3, session_cleanup/5, remove_domain/3
60 ]).
61
62 -include("mongoose.hrl").
63 -include("mongoose_config_spec.hrl").
64
65 -include("jlib.hrl").
66
67 %% ------------------------------------------------------------------
68 %% Backend callbacks
69
70 -export_type([timestamp/0, status/0]).
71
72 -type timestamp() :: non_neg_integer().
73 -type status() :: binary().
74
75 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
76 start(HostType, #{iqdisc := IQDisc} = Opts) ->
77
78 6 mod_last_backend:init(HostType, Opts),
79 6 [gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_LAST, Component, Fn, #{}, IQDisc) ||
80 6 {Component, Fn} <- iq_handlers()],
81 6 ejabberd_hooks:add(hooks(HostType)).
82
83 -spec stop(mongooseim:host_type()) -> ok.
84 stop(HostType) ->
85 6 ejabberd_hooks:delete(hooks(HostType)),
86 6 [gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_LAST, Component) ||
87 6 {Component, _Fn} <- iq_handlers()],
88 6 ok.
89
90 iq_handlers() ->
91 12 [{ejabberd_local, fun ?MODULE:process_local_iq/5},
92 {ejabberd_sm, fun ?MODULE:process_sm_iq/5}].
93
94 hooks(HostType) ->
95 12 [{remove_user, HostType, ?MODULE, remove_user, 50},
96 {anonymous_purge_hook, HostType, ?MODULE, remove_user, 50},
97 {unset_presence_hook, HostType, ?MODULE, on_presence_update, 50},
98 {session_cleanup, HostType, ?MODULE, session_cleanup, 50},
99 {remove_domain, HostType, ?MODULE, remove_domain, 50}].
100
101 %%%
102 %%% config_spec
103 %%%
104
105 -spec config_spec() -> mongoose_config_spec:config_section().
106 config_spec() ->
107 166 #section{
108 items = #{<<"iqdisc">> => mongoose_config_spec:iqdisc(),
109 <<"backend">> => #option{type = atom,
110 validate = {module, mod_last}},
111 <<"riak">> => riak_config_spec()
112 },
113 defaults = #{<<"iqdisc">> => one_queue,
114 <<"backend">> => mnesia
115 },
116 process = fun ?MODULE:remove_unused_backend_opts/1
117 }.
118
119
:-(
remove_unused_backend_opts(Opts = #{backend := riak}) -> Opts;
120
:-(
remove_unused_backend_opts(Opts) -> maps:remove(riak, Opts).
121
122 riak_config_spec() ->
123 166 #section{items = #{<<"bucket_type">> => #option{type = binary,
124 validate = non_empty}
125 },
126 defaults = #{<<"bucket_type">> => <<"last">>},
127 include = always
128 }.
129
130
:-(
supported_features() -> [dynamic_domains].
131
132 %%%
133 %%% Uptime of ejabberd node
134 %%%
135 -spec process_local_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map())
136 -> {mongoose_acc:t(), jlib:iq()}.
137 process_local_iq(Acc, _From, _To, #iq{type = Type, sub_el = SubEl} = IQ, _Extra) ->
138 1 case Type of
139 set ->
140
:-(
{Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}};
141 get ->
142 1 Sec = get_node_uptime(),
143 1 {Acc, IQ#iq{type = result,
144 sub_el =
145 [#xmlel{name = <<"query">>,
146 attrs =
147 [{<<"xmlns">>, ?NS_LAST},
148 {<<"seconds">>,
149 integer_to_binary(Sec)}],
150 children = []}]}}
151 end.
152
153 -spec get_node_uptime() -> non_neg_integer().
154 get_node_uptime() ->
155 1 case mongoose_config:lookup_opt(node_start) of
156 {ok, {node_start, Seconds}} ->
157
:-(
erlang:system_time(second) - Seconds;
158 {error, not_found} ->
159 1 trunc(element(1, erlang:statistics(wall_clock))/1000)
160 end.
161
162 %%%
163 %%% Serve queries about user last online
164 %%%
165 -spec process_sm_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) ->
166 {mongoose_acc:t(), jlib:iq()}.
167 process_sm_iq(Acc, _From, _To, #iq{type = set, sub_el = SubEl} = IQ, _Extra) ->
168
:-(
{Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}};
169 process_sm_iq(Acc, From, To, #iq{type = get, sub_el = SubEl} = IQ, _Extra) ->
170 4 HostType = mongoose_acc:host_type(Acc),
171 4 {Subscription, _Groups} = mongoose_hooks:roster_get_jid_info(HostType, To, From),
172 4 MutualSubscription = Subscription == both,
173 4 RequesterSubscribedToTarget = Subscription == from,
174 4 QueryingSameUsersLast = (From#jid.luser == To#jid.luser) and
175 (From#jid.lserver == To#jid.lserver),
176 4 case MutualSubscription or RequesterSubscribedToTarget or QueryingSameUsersLast of
177 true ->
178 3 UserListRecord = mongoose_hooks:privacy_get_user_list(HostType, To),
179 3 {Acc1, Res} = mongoose_privacy:privacy_check_packet(Acc, To,
180 UserListRecord, To, From,
181 out),
182 3 {Acc1, make_response(HostType, IQ, SubEl, To, Res)};
183 false ->
184 1 {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:forbidden()]}}
185 end.
186
187 -spec make_response(mongooseim:host_type(), jlib:iq(), SubEl :: 'undefined' | [exml:element()],
188 jid:jid(), allow | deny) -> jlib:iq().
189 make_response(_HostType, IQ, SubEl, _, deny) ->
190
:-(
IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:forbidden()]};
191 make_response(HostType, IQ, SubEl, JID, allow) ->
192 3 #jid{luser = LUser, lserver = LServer} = JID,
193 3 case ejabberd_sm:get_user_resources(JID) of
194 [] ->
195 2 case get_last(HostType, LUser, LServer) of
196 {error, _Reason} ->
197
:-(
IQ#iq{type = error,
198 sub_el = [SubEl, mongoose_xmpp_errors:internal_server_error()]};
199 not_found ->
200
:-(
IQ#iq{type = error,
201 sub_el = [SubEl, mongoose_xmpp_errors:service_unavailable()]};
202 {ok, TimeStamp, Status} ->
203 2 TimeStamp2 = erlang:system_time(second),
204 2 Sec = TimeStamp2 - TimeStamp,
205 2 IQ#iq{type = result,
206 sub_el =
207 [#xmlel{name = <<"query">>,
208 attrs =
209 [{<<"xmlns">>, ?NS_LAST},
210 {<<"seconds">>,
211 integer_to_binary(Sec)}],
212 children = [{xmlcdata, Status}]}]}
213 end;
214 _ ->
215 1 IQ#iq{type = result,
216 sub_el =
217 [#xmlel{name = <<"query">>,
218 attrs =
219 [{<<"xmlns">>, ?NS_LAST},
220 {<<"seconds">>, <<"0">>}],
221 children = []}]}
222 end.
223
224 -spec get_last_info(mongooseim:host_type(), jid:luser(), jid:lserver())
225 -> 'not_found' | {'ok', timestamp(), status()}.
226 get_last_info(HostType, LUser, LServer) ->
227 34 case get_last(HostType, LUser, LServer) of
228
:-(
{error, _Reason} -> not_found;
229 34 Res -> Res
230 end.
231
232 -spec remove_user(mongoose_acc:t(), jid:user(), jid:server()) -> mongoose_acc:t().
233 remove_user(Acc, User, Server) ->
234 62 HostType = mongoose_acc:host_type(Acc),
235 62 LUser = jid:nodeprep(User),
236 62 LServer = jid:nameprep(Server),
237 62 R = mod_last_backend:remove_user(HostType, LUser, LServer),
238 62 mongoose_lib:log_if_backend_error(R, ?MODULE, ?LINE, {Acc, User, Server}),
239 62 Acc.
240
241 -spec remove_domain(mongoose_hooks:simple_acc(), mongooseim:host_type(), jid:lserver()) ->
242 mongoose_hooks:simple_acc().
243 remove_domain(Acc, HostType, Domain) ->
244
:-(
mod_last_backend:remove_domain(HostType, Domain),
245
:-(
Acc.
246
247 -spec on_presence_update(mongoose_acc:t(), jid:luser(), jid:lserver(), jid:lresource(), status()) ->
248 mongoose_acc:t().
249 on_presence_update(Acc, LUser, LServer, _Resource, Status) ->
250 71 store_last_info(Acc, LUser, LServer, Status).
251
252 -spec session_cleanup(mongoose_acc:t(), jid:luser(), jid:lserver(), jid:lresource(),
253 ejabberd_sm:sid()) ->
254 mongoose_acc:t().
255 session_cleanup(Acc, LUser, LServer, _LResource, _SID) ->
256
:-(
store_last_info(Acc, LUser, LServer, <<>>).
257
258 -spec store_last_info(mongoose_acc:t(), jid:luser(), jid:lserver(), status()) -> mongoose_acc:t().
259 store_last_info(Acc, LUser, LServer, Status) ->
260 71 HostType = mongoose_acc:host_type(Acc),
261 71 TimeStamp = erlang:system_time(second),
262 71 store_last_info(HostType, LUser, LServer, TimeStamp, Status),
263 71 Acc.
264
265 -spec store_last_info(mongooseim:host_type(), jid:luser(), jid:lserver(),
266 timestamp(), status()) -> ok.
267 store_last_info(HostType, LUser, LServer, TimeStamp, Status) ->
268 100 case mod_last_backend:set_last_info(HostType, LUser, LServer, TimeStamp, Status) of
269 {error, Reason} ->
270
:-(
?LOG_ERROR(#{what => set_last_info_failed,
271 text => <<"Unexpected error while storing mod_last information">>,
272 user => LUser, server => LServer,
273 timestamp => TimeStamp, status => Status,
274
:-(
reason => Reason});
275 ok ->
276 100 ok
277 end.
278
279 -spec get_last(mongooseim:host_type(), jid:luser(), jid:lserver()) ->
280 {ok, timestamp(), status()} | {error, term()} | not_found.
281 get_last(HostType, LUser, LServer) ->
282 36 mod_last_backend:get_last(HostType, LUser, LServer).
283
284 -spec count_active_users(mongooseim:host_type(), jid:lserver(), timestamp()) -> non_neg_integer().
285 count_active_users(HostType, LServer, Timestamp) ->
286 4 mod_last_backend:count_active_users(HostType, LServer, Timestamp).
287
288 -spec config_metrics(mongooseim:host_type()) -> [{gen_mod:opt_key(), gen_mod:opt_value()}].
289 config_metrics(HostType) ->
290 12 mongoose_module_metrics:opts_for_module(HostType, ?MODULE, [backend]).
Line Hits Source