./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 5 mod_last_backend:init(HostType, Opts),
79 5 [gen_iq_handler:add_iq_handler_for_domain(HostType, ?NS_LAST, Component, Fn, #{}, IQDisc) ||
80 5 {Component, Fn} <- iq_handlers()],
81 5 ejabberd_hooks:add(hooks(HostType)).
82
83 -spec stop(mongooseim:host_type()) -> ok.
84 stop(HostType) ->
85 5 ejabberd_hooks:delete(hooks(HostType)),
86 5 [gen_iq_handler:remove_iq_handler_for_domain(HostType, ?NS_LAST, Component) ||
87 5 {Component, _Fn} <- iq_handlers()],
88 5 ok.
89
90 iq_handlers() ->
91 10 [{ejabberd_local, fun ?MODULE:process_local_iq/5},
92 {ejabberd_sm, fun ?MODULE:process_sm_iq/5}].
93
94 hooks(HostType) ->
95 10 [{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 152 #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 format_items = map,
117 process = fun ?MODULE:remove_unused_backend_opts/1
118 }.
119
120
:-(
remove_unused_backend_opts(Opts = #{backend := riak}) -> Opts;
121
:-(
remove_unused_backend_opts(Opts) -> maps:remove(riak, Opts).
122
123 riak_config_spec() ->
124 152 #section{items = #{<<"bucket_type">> => #option{type = binary,
125 validate = non_empty}
126 },
127 defaults = #{<<"bucket_type">> => <<"last">>},
128 include = always,
129 format_items = map
130 }.
131
132 7 supported_features() -> [dynamic_domains].
133
134 %%%
135 %%% Uptime of ejabberd node
136 %%%
137 -spec process_local_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map())
138 -> {mongoose_acc:t(), jlib:iq()}.
139 process_local_iq(Acc, _From, _To, #iq{type = Type, sub_el = SubEl} = IQ, _Extra) ->
140 1 case Type of
141 set ->
142
:-(
{Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}};
143 get ->
144 1 Sec = get_node_uptime(),
145 1 {Acc, IQ#iq{type = result,
146 sub_el =
147 [#xmlel{name = <<"query">>,
148 attrs =
149 [{<<"xmlns">>, ?NS_LAST},
150 {<<"seconds">>,
151 integer_to_binary(Sec)}],
152 children = []}]}}
153 end.
154
155 -spec get_node_uptime() -> non_neg_integer().
156 get_node_uptime() ->
157 1 case mongoose_config:lookup_opt(node_start) of
158 {ok, {node_start, Seconds}} ->
159
:-(
erlang:system_time(second) - Seconds;
160 {error, not_found} ->
161 1 trunc(element(1, erlang:statistics(wall_clock))/1000)
162 end.
163
164 %%%
165 %%% Serve queries about user last online
166 %%%
167 -spec process_sm_iq(mongoose_acc:t(), jid:jid(), jid:jid(), jlib:iq(), map()) ->
168 {mongoose_acc:t(), jlib:iq()}.
169 process_sm_iq(Acc, _From, _To, #iq{type = set, sub_el = SubEl} = IQ, _Extra) ->
170
:-(
{Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:not_allowed()]}};
171 process_sm_iq(Acc, From, To, #iq{type = get, sub_el = SubEl} = IQ, _Extra) ->
172 6 HostType = mongoose_acc:host_type(Acc),
173 6 {Subscription, _Groups} = mongoose_hooks:roster_get_jid_info(HostType, To, From),
174 6 MutualSubscription = Subscription == both,
175 6 RequesterSubscribedToTarget = Subscription == from,
176 6 QueryingSameUsersLast = (From#jid.luser == To#jid.luser) and
177 (From#jid.lserver == To#jid.lserver),
178 6 case MutualSubscription or RequesterSubscribedToTarget or QueryingSameUsersLast of
179 true ->
180 4 UserListRecord = mongoose_hooks:privacy_get_user_list(HostType, To),
181 4 {Acc1, Res} = mongoose_privacy:privacy_check_packet(Acc, To,
182 UserListRecord, To, From,
183 out),
184 4 {Acc1, make_response(HostType, IQ, SubEl, To, Res)};
185 false ->
186 2 {Acc, IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:forbidden()]}}
187 end.
188
189 -spec make_response(mongooseim:host_type(), jlib:iq(), SubEl :: 'undefined' | [exml:element()],
190 jid:jid(), allow | deny) -> jlib:iq().
191 make_response(_HostType, IQ, SubEl, _, deny) ->
192
:-(
IQ#iq{type = error, sub_el = [SubEl, mongoose_xmpp_errors:forbidden()]};
193 make_response(HostType, IQ, SubEl, JID, allow) ->
194 4 #jid{luser = LUser, lserver = LServer} = JID,
195 4 case ejabberd_sm:get_user_resources(JID) of
196 [] ->
197 3 case get_last(HostType, LUser, LServer) of
198 {error, _Reason} ->
199
:-(
IQ#iq{type = error,
200 sub_el = [SubEl, mongoose_xmpp_errors:internal_server_error()]};
201 not_found ->
202
:-(
IQ#iq{type = error,
203 sub_el = [SubEl, mongoose_xmpp_errors:service_unavailable()]};
204 {ok, TimeStamp, Status} ->
205 3 TimeStamp2 = erlang:system_time(second),
206 3 Sec = TimeStamp2 - TimeStamp,
207 3 IQ#iq{type = result,
208 sub_el =
209 [#xmlel{name = <<"query">>,
210 attrs =
211 [{<<"xmlns">>, ?NS_LAST},
212 {<<"seconds">>,
213 integer_to_binary(Sec)}],
214 children = [{xmlcdata, Status}]}]}
215 end;
216 _ ->
217 1 IQ#iq{type = result,
218 sub_el =
219 [#xmlel{name = <<"query">>,
220 attrs =
221 [{<<"xmlns">>, ?NS_LAST},
222 {<<"seconds">>, <<"0">>}],
223 children = []}]}
224 end.
225
226 -spec get_last_info(mongooseim:host_type(), jid:luser(), jid:lserver())
227 -> 'not_found' | {'ok', integer(), binary()}.
228 get_last_info(HostType, LUser, LServer) ->
229 36 case get_last(HostType, LUser, LServer) of
230
:-(
{error, _Reason} -> not_found;
231 36 Res -> Res
232 end.
233
234 -spec remove_user(mongoose_acc:t(), jid:user(), jid:server()) -> mongoose_acc:t().
235 remove_user(Acc, User, Server) ->
236 54 HostType = mongoose_acc:host_type(Acc),
237 54 LUser = jid:nodeprep(User),
238 54 LServer = jid:nameprep(Server),
239 54 R = mod_last_backend:remove_user(HostType, LUser, LServer),
240 54 mongoose_lib:log_if_backend_error(R, ?MODULE, ?LINE, {Acc, User, Server}),
241 54 Acc.
242
243 -spec remove_domain(mongoose_hooks:simple_acc(), mongooseim:host_type(), jid:lserver()) -> mongoose_hooks:simple_acc().
244 remove_domain(Acc, HostType, Domain) ->
245 1 mod_last_backend:remove_domain(HostType, Domain),
246 1 Acc.
247
248 -spec on_presence_update(mongoose_acc:t(), jid:luser(), jid:lserver(), jid:lresource(), status()) ->
249 mongoose_acc:t().
250 on_presence_update(Acc, LUser, LServer, _Resource, Status) ->
251 49 store_last_info(Acc, LUser, LServer, Status).
252
253 -spec session_cleanup(mongoose_acc:t(), jid:luser(), jid:lserver(), jid:lresource(),
254 ejabberd_sm:sid()) ->
255 mongoose_acc:t().
256 session_cleanup(Acc, LUser, LServer, _LResource, _SID) ->
257
:-(
store_last_info(Acc, LUser, LServer, <<>>).
258
259 -spec store_last_info(mongoose_acc:t(), jid:luser(), jid:lserver(), status()) -> mongoose_acc:t().
260 store_last_info(Acc, LUser, LServer, Status) ->
261 49 HostType = mongoose_acc:host_type(Acc),
262 49 TimeStamp = erlang:system_time(second),
263 49 store_last_info(HostType, LUser, LServer, TimeStamp, Status),
264 49 Acc.
265
266 -spec store_last_info(mongooseim:host_type(), jid:luser(), jid:lserver(), timestamp(), status()) -> ok.
267 store_last_info(HostType, LUser, LServer, TimeStamp, Status) ->
268 68 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 68 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 39 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 3 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 30 mongoose_module_metrics:opts_for_module(HostType, ?MODULE, [backend]).
Line Hits Source