./ct_report/coverage/mod_offline.COVER.html

1 %%%----------------------------------------------------------------------
2 %%% File : mod_offline.erl
3 %%% Author : Alexey Shchepin <alexey@process-one.net>
4 %%% Purpose : Store and manage offline messages
5 %%% See : XEP-0160: Best Practices for Handling Offline Messages
6 %%% Created : 5 Jan 2003 by Alexey Shchepin <alexey@process-one.net>
7 %%%
8 %%%
9 %%% ejabberd, Copyright (C) 2002-2011 ProcessOne
10 %%%
11 %%% This program is free software; you can redistribute it and/or
12 %%% modify it under the terms of the GNU General Public License as
13 %%% published by the Free Software Foundation; either version 2 of the
14 %%% License, or (at your option) any later version.
15 %%%
16 %%% This program is distributed in the hope that it will be useful,
17 %%% but WITHOUT ANY WARRANTY; without even the implied warranty of
18 %%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 %%% General Public License for more details.
20 %%%
21 %%% You should have received a copy of the GNU General Public License
22 %%% along with this program; if not, write to the Free Software
23 %%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
24 %%%
25 %%%----------------------------------------------------------------------
26
27 -module(mod_offline).
28 -author('alexey@process-one.net').
29 -xep([{xep, 160}, {version, "1.0"}]).
30 -xep([{xep, 23}, {version, "1.3"}]).
31 -xep([{xep, 22}, {version, "1.4"}]).
32 -xep([{xep, 85}, {version, "2.1"}]).
33 -behaviour(gen_mod).
34 -behaviour(mongoose_module_metrics).
35
36 %% gen_mod handlers
37 -export([start/2, stop/1, config_spec/0, supported_features/0]).
38
39 %% Hook handlers
40 -export([inspect_packet/4,
41 pop_offline_messages/2,
42 remove_user/3,
43 remove_domain/3,
44 disco_features/1,
45 determine_amp_strategy/5,
46 amp_failed_event/1,
47 get_personal_data/3]).
48
49 %% Admin API
50 -export([remove_expired_messages/2,
51 remove_old_messages/3]).
52
53 %% Internal exports
54 -export([start_link/3]).
55
56 %% gen_server callbacks
57 -export([init/1, handle_call/3, handle_cast/2, handle_info/2,
58 terminate/2, code_change/3]).
59
60 %% helpers to be used from backend moudules
61 -export([is_expired_message/2]).
62
63 -export([config_metrics/1]).
64
65 -ignore_xref([
66 amp_failed_event/1, behaviour_info/1, code_change/3, determine_amp_strategy/5,
67 disco_features/1, get_personal_data/3, handle_call/3, handle_cast/2,
68 handle_info/2, init/1, inspect_packet/4, pop_offline_messages/2, remove_user/2,
69 remove_user/3, remove_domain/3, start_link/3, terminate/2
70 ]).
71
72 -include("mongoose.hrl").
73 -include("jlib.hrl").
74 -include("amp.hrl").
75 -include("mod_offline.hrl").
76 -include("mongoose_config_spec.hrl").
77
78 %% default value for the maximum number of user messages
79 -define(MAX_USER_MESSAGES, infinity).
80
81 -type msg() :: #offline_msg{us :: {jid:luser(), jid:lserver()},
82 timestamp :: integer(),
83 expire :: integer() | never,
84 from :: jid:jid(),
85 to :: jid:jid(),
86 packet :: exml:element()}.
87
88 -export_type([msg/0]).
89
90 -type poppers() :: monitored_map:t({jid:luser(), jid:lserver()}, pid()).
91
92 -record(state, {host_type :: mongooseim:host_type(),
93 access_max_user_messages :: atom(),
94 message_poppers = monitored_map:new() :: poppers()
95 }).
96
97 -type state() :: #state{}.
98
99 %% Types used in backend callbacks
100 -type msg_count() :: non_neg_integer().
101 -type timestamp() :: integer().
102
103 -export_type([msg_count/0, timestamp/0]).
104
105 %% gen_mod callbacks
106 %% ------------------------------------------------------------------
107
108 start(HostType, Opts) ->
109 10 AccessMaxOfflineMsgs = gen_mod:get_opt(access_max_user_messages, Opts,
110 max_user_offline_messages),
111 10 mod_offline_backend:init(HostType, Opts),
112 10 start_worker(HostType, AccessMaxOfflineMsgs),
113 10 ejabberd_hooks:add(hooks(HostType)),
114 10 ok.
115
116 stop(Host) ->
117 10 ejabberd_hooks:delete(hooks(Host)),
118 10 stop_worker(Host),
119 10 ok.
120
121 -spec config_spec() -> mongoose_config_spec:config_section().
122 config_spec() ->
123 160 #section{
124 items = #{<<"access_max_user_messages">> => #option{type = atom,
125 validate = access_rule},
126 <<"backend">> => #option{type = atom,
127 validate = {module, mod_offline}},
128 <<"riak">> => riak_config_spec()
129 }
130 }.
131
132 riak_config_spec() ->
133 160 #section{items = #{<<"bucket_type">> => #option{type = binary,
134 validate = non_empty}},
135 wrap = none
136 }.
137
138
:-(
supported_features() -> [dynamic_domains].
139
140 hooks(HostType) ->
141 20 DefaultHooks = [
142 {offline_message_hook, HostType, ?MODULE, inspect_packet, 50},
143 {resend_offline_messages_hook, HostType, ?MODULE, pop_offline_messages, 50},
144 {remove_user, HostType, ?MODULE, remove_user, 50},
145 {remove_domain, HostType, ?MODULE, remove_domain, 50},
146 {anonymous_purge_hook, HostType, ?MODULE, remove_user, 50},
147 {disco_sm_features, HostType, ?MODULE, disco_features, 50},
148 {disco_local_features, HostType, ?MODULE, disco_features, 50},
149 {amp_determine_strategy, HostType, ?MODULE, determine_amp_strategy, 30},
150 {failed_to_store_message, HostType, ?MODULE, amp_failed_event, 30},
151 {get_personal_data, HostType, ?MODULE, get_personal_data, 50}
152 ],
153 20 case gen_mod:get_module_opt(HostType, ?MODULE, store_groupchat_messages, false) of
154 true ->
155 2 GroupChatHook = {offline_groupchat_message_hook,
156 HostType, ?MODULE, inspect_packet, 50},
157 2 [GroupChatHook | DefaultHooks];
158 18 _ -> DefaultHooks
159 end.
160
161
162
163 %% Server side functions
164 %% ------------------------------------------------------------------
165
166 amp_failed_event(Acc) ->
167 17 mod_amp:check_packet(Acc, offline_failed).
168
169 handle_offline_msg(HostType, Acc, #offline_msg{us=US} = Msg, AccessMaxOfflineMsgs) ->
170 117 {LUser, LServer} = US,
171 117 Msgs = receive_all(US, [{Acc, Msg}]),
172 117 MaxOfflineMsgs = get_max_user_messages(HostType, AccessMaxOfflineMsgs, LUser, LServer),
173 117 Len = length(Msgs),
174 117 case is_message_count_threshold_reached(HostType, MaxOfflineMsgs, LUser, LServer, Len) of
175 false ->
176 116 write_messages(HostType, LUser, LServer, Msgs);
177 true ->
178 1 discard_warn_sender(Msgs)
179 end.
180
181 write_messages(HostType, LUser, LServer, Msgs) ->
182 116 MsgsWithoutAcc = [Msg || {_Acc, Msg} <- Msgs],
183 116 case mod_offline_backend:write_messages(HostType, LUser, LServer, MsgsWithoutAcc) of
184 ok ->
185 108 [mod_amp:check_packet(Acc, archived) || {Acc, _Msg} <- Msgs],
186 108 ok;
187 {error, Reason} ->
188 8 ?LOG_ERROR(#{what => offline_write_failed,
189 text => <<"Failed to write offline messages">>,
190 reason => Reason,
191
:-(
user => LUser, server => LServer, msgs => Msgs}),
192 8 discard_warn_sender(Msgs)
193 end.
194
195 -spec is_message_count_threshold_reached(mongooseim:host_type(), integer() | infinity,
196 jid:luser(), jid:lserver(), integer()) ->
197 boolean().
198 is_message_count_threshold_reached(_HostType, infinity, _LUser, _LServer, _Len) ->
199
:-(
false;
200 is_message_count_threshold_reached(_HostType, MaxOfflineMsgs, _LUser, _LServer, Len)
201 when Len > MaxOfflineMsgs ->
202
:-(
true;
203 is_message_count_threshold_reached(HostType, MaxOfflineMsgs, LUser, LServer, Len) ->
204 %% Only count messages if needed.
205 117 MaxArchivedMsg = MaxOfflineMsgs - Len,
206 %% Maybe do not need to count all messages in archive
207 117 MaxArchivedMsg < mod_offline_backend:count_offline_messages(HostType, LUser, LServer,
208 MaxArchivedMsg + 1).
209
210
211 get_max_user_messages(HostType, AccessRule, LUser, LServer) ->
212 117 case acl:match_rule(HostType, LServer, AccessRule, jid:make_noprep(LUser, LServer, <<>>)) of
213 117 Max when is_integer(Max) -> Max;
214
:-(
infinity -> infinity;
215
:-(
_ -> ?MAX_USER_MESSAGES
216 end.
217
218 receive_all(US, Msgs) ->
219 185 receive
220 {_Acc, #offline_msg{us=US}} = Msg ->
221 68 receive_all(US, [Msg | Msgs])
222 after 0 ->
223 117 Msgs
224 end.
225
226 %% Supervision
227 %% ------------------------------------------------------------------
228
229 start_worker(HostType, AccessMaxOfflineMsgs) ->
230 10 Proc = srv_name(HostType),
231 10 ChildSpec =
232 {Proc,
233 {?MODULE, start_link, [Proc, HostType, AccessMaxOfflineMsgs]},
234 permanent, 5000, worker, [?MODULE]},
235 10 ejabberd_sup:start_child(ChildSpec).
236
237 stop_worker(HostType) ->
238 10 Proc = srv_name(HostType),
239 10 ejabberd_sup:stop_child(Proc).
240
241 start_link(Name, HostType, AccessMaxOfflineMsgs) ->
242 10 gen_server:start_link({local, Name}, ?MODULE, [HostType, AccessMaxOfflineMsgs], []).
243
244 srv_name() ->
245 555 mod_offline.
246
247 srv_name(HostType) ->
248 555 gen_mod:get_module_proc(HostType, srv_name()).
249
250 %%====================================================================
251 %% gen_server callbacks
252 %%====================================================================
253
254 -spec init(list()) -> {ok, state()}.
255 init([HostType, AccessMaxOfflineMsgs]) ->
256 10 {ok, #state{host_type = HostType,
257 access_max_user_messages = AccessMaxOfflineMsgs}}.
258
259 -spec handle_call(Request :: any(), {pid(), any()}, state()) -> {reply, Result, state()}
260 when Result :: ok | {ok, [msg()]} | {error, any()}.
261 handle_call({pop_offline_messages, JID}, {Pid, _}, State = #state{host_type = HostType}) ->
262 350 Result = mod_offline_backend:pop_messages(HostType, JID),
263 350 NewPoppers = monitored_map:put(jid:to_lus(JID), Pid, Pid, State#state.message_poppers),
264 350 {reply, Result, State#state{message_poppers = NewPoppers}};
265 handle_call(Request, From, State) ->
266
:-(
?UNEXPECTED_CALL(Request, From),
267
:-(
{reply, ok, State}.
268
269 -spec handle_cast(any(), state()) -> {noreply, state()}.
270 handle_cast(Msg, State) ->
271
:-(
?UNEXPECTED_CAST(Msg),
272
:-(
{noreply, State}.
273
274 -spec handle_info(any(), state()) -> {noreply, state()}.
275 handle_info({'DOWN', _MonitorRef, _Type, _Object, _Info} = Msg, State) ->
276 343 NewPoppers = monitored_map:handle_info(Msg, State#state.message_poppers),
277 343 {noreply, State#state{message_poppers = NewPoppers}};
278 handle_info({Acc, Msg = #offline_msg{us = US}},
279 State = #state{host_type = HostType,
280 access_max_user_messages = AccessMaxOfflineMsgs}) ->
281 117 handle_offline_msg(HostType, Acc, Msg, AccessMaxOfflineMsgs),
282 117 case monitored_map:find(US, State#state.message_poppers) of
283 {ok, Pid} ->
284 27 Pid ! new_offline_messages;
285 90 error -> ok
286 end,
287 117 {noreply, State};
288 handle_info(Msg, State) ->
289
:-(
?UNEXPECTED_INFO(Msg),
290
:-(
{noreply, State}.
291
292 -spec terminate(any(), state()) -> ok.
293 terminate(_Reason, _State) ->
294
:-(
ok.
295
296 -spec code_change(any(), state(), any()) -> {ok, state()}.
297 code_change(_OldVsn, State, _Extra) ->
298
:-(
{ok, State}.
299
300 %% Handlers
301 %% ------------------------------------------------------------------
302
303 %% This function should be called only from a hook
304 %% Calling it directly is dangerous and may store unwanted messages
305 %% in the offline storage (e.g. messages of type error)
306 -spec inspect_packet(mongoose_acc:t(), jid:jid(), jid:jid(), exml:element()) -> mongoose_acc:t().
307 inspect_packet(Acc, From, To, Packet) ->
308 185 case check_event_chatstates(Acc, From, To, Packet) of
309 true ->
310 185 Acc1 = store_packet(Acc, From, To, Packet),
311 185 {stop, Acc1};
312 false ->
313
:-(
Acc
314 end.
315
316 -spec store_packet(mongoose_acc:t(), jid:jid(), jid:jid(), exml:element()) -> mongoose_acc:t().
317 store_packet(Acc, From, To = #jid{luser = LUser, lserver = LServer},
318 Packet = #xmlel{children = Els}) ->
319 185 TimeStamp = get_or_build_timestamp_from_packet(Packet),
320 185 Expire = find_x_expire(TimeStamp, Els),
321 185 HostType = mongoose_acc:host_type(Acc),
322 185 Pid = srv_name(HostType),
323 185 PermanentFields = mongoose_acc:get_permanent_fields(Acc),
324 185 Msg = #offline_msg{us = {LUser, LServer},
325 timestamp = TimeStamp,
326 expire = Expire,
327 from = From,
328 to = To,
329 packet = jlib:remove_delay_tags(Packet),
330 permanent_fields = PermanentFields},
331 185 Pid ! {Acc, Msg},
332 185 mongoose_acc:set(offline, stored, true, Acc).
333
334 -spec get_or_build_timestamp_from_packet(exml:element()) -> integer().
335 get_or_build_timestamp_from_packet(Packet) ->
336 185 case exml_query:path(Packet, [{element, <<"delay">>}, {attr, <<"stamp">>}]) of
337 undefined ->
338 145 erlang:system_time(microsecond);
339 Stamp ->
340 40 try
341 40 calendar:rfc3339_to_system_time(binary_to_list(Stamp), [{unit, microsecond}])
342 catch
343
:-(
error:_Error -> erlang:system_time(microsecond)
344 end
345 end.
346
347 %% Check if the packet has any content about XEP-0022 or XEP-0085
348 check_event_chatstates(Acc, From, To, Packet) ->
349 185 #xmlel{children = Els} = Packet,
350 185 case find_x_event_chatstates(Els, {false, false, false}) of
351 %% There wasn't any x:event or chatstates subelements
352 {false, false, _} ->
353 185 true;
354 %% There a chatstates subelement and other stuff, but no x:event
355 {false, CEl, true} when CEl /= false ->
356
:-(
true;
357 %% There was only a subelement: a chatstates
358 {false, CEl, false} when CEl /= false ->
359 %% Don't allow offline storage
360
:-(
false;
361 %% There was an x:event element, and maybe also other stuff
362 {El, _, _} when El /= false ->
363
:-(
inspect_xevent(Acc, From, To, Packet, El)
364 end.
365
366 inspect_xevent(Acc, From, To, Packet, XEvent) ->
367
:-(
case exml_query:subelement(XEvent, <<"id">>) of
368 undefined ->
369
:-(
case exml_query:subelement(XEvent, <<"offline">>) of
370 undefined ->
371
:-(
true;
372 _ ->
373
:-(
ejabberd_router:route(To, From, Acc, patch_offline_message(Packet)),
374
:-(
true
375 end;
376 _ ->
377
:-(
false
378 end.
379
380 patch_offline_message(Packet) ->
381
:-(
ID = case exml_query:attr(Packet, <<"id">>, <<>>) of
382
:-(
<<"">> -> #xmlel{name = <<"id">>};
383
:-(
S -> #xmlel{name = <<"id">>, children = [#xmlcdata{content = S}]}
384 end,
385
:-(
Packet#xmlel{children = [x_elem(ID)]}.
386
387 x_elem(ID) ->
388
:-(
#xmlel{
389 name = <<"x">>,
390 attrs = [{<<"xmlns">>, ?NS_EVENT}],
391 children = [ID, #xmlel{name = <<"offline">>}]}.
392
393 %% Check if the packet has subelements about XEP-0022, XEP-0085 or other
394 find_x_event_chatstates([], Res) ->
395 185 Res;
396 find_x_event_chatstates([#xmlcdata{} | Els], Res) ->
397
:-(
find_x_event_chatstates(Els, Res);
398 find_x_event_chatstates([El | Els], {A, B, C}) ->
399 228 case exml_query:attr(El, <<"xmlns">>, <<>>) of
400
:-(
?NS_EVENT -> find_x_event_chatstates(Els, {El, B, C});
401
:-(
?NS_CHATSTATES -> find_x_event_chatstates(Els, {A, El, C});
402 228 _ -> find_x_event_chatstates(Els, {A, B, true})
403 end.
404
405 find_x_expire(_, []) ->
406 183 never;
407 find_x_expire(TimeStamp, [#xmlcdata{} | Els]) ->
408
:-(
find_x_expire(TimeStamp, Els);
409 find_x_expire(TimeStamp, [El | Els]) ->
410 226 case exml_query:attr(El, <<"xmlns">>, <<>>) of
411 ?NS_EXPIRE ->
412 2 Val = exml_query:attr(El, <<"seconds">>, <<>>),
413 2 try binary_to_integer(Val) of
414 Int when Int > 0 ->
415 2 ExpireMicroSeconds = erlang:convert_time_unit(Int, second, microsecond),
416 2 TimeStamp + ExpireMicroSeconds;
417 _ ->
418
:-(
never
419 catch
420
:-(
error:badarg -> never
421 end;
422 _ ->
423 224 find_x_expire(TimeStamp, Els)
424 end.
425
426 -spec pop_offline_messages(mongoose_acc:t(), jid:jid()) -> mongoose_acc:t().
427 pop_offline_messages(Acc, JID) ->
428 350 mongoose_acc:append(offline, messages, offline_messages(Acc, JID), Acc).
429
430 -spec offline_messages(mongoose_acc:t(), jid:jid()) ->
431 [{route, jid:jid(), jid:jid(), mongoose_acc:t()}].
432 offline_messages(Acc, #jid{lserver = LServer} = JID) ->
433 350 HostType = mongoose_acc:host_type(Acc),
434 350 case pop_messages(HostType, JID) of
435 {ok, Rs} ->
436 350 lists:map(fun(R) ->
437 135 Packet = resend_offline_message_packet(LServer, R),
438 135 compose_offline_message(R, Packet, Acc)
439 end, Rs);
440 {error, Reason} ->
441
:-(
?LOG_WARNING(#{what => offline_pop_failed, reason => Reason, acc => Acc}),
442
:-(
[]
443 end.
444
445 -spec pop_messages(mongooseim:host_type(), jid:jid()) -> {ok, [msg()]} | {error, any()}.
446 pop_messages(HostType, JID) ->
447 350 case gen_server:call(srv_name(HostType), {pop_offline_messages, jid:to_bare(JID)}) of
448 {ok, RsAll} ->
449 350 TimeStamp = erlang:system_time(microsecond),
450 350 Rs = skip_expired_messages(TimeStamp, lists:keysort(#offline_msg.timestamp, RsAll)),
451 350 {ok, Rs};
452 Other ->
453
:-(
Other
454 end.
455
456 -spec remove_user(mongoose_acc:t(), jid:luser(), jid:lserver()) -> mongoose_acc:t().
457 remove_user(Acc, LUser, LServer) ->
458 145 HostType = mongoose_acc:host_type(Acc),
459 145 mod_offline_backend:remove_user(HostType, LUser, LServer),
460 145 Acc.
461
462 -spec remove_domain(mongoose_hooks:simple_acc(),
463 mongooseim:host_type(), jid:lserver()) ->
464 mongoose_hooks:simple_acc().
465 remove_domain(Acc, HostType, Domain) ->
466
:-(
case backend_module:is_exported(mod_offline_backend, remove_domain, 2) of
467 true ->
468
:-(
mod_offline_backend:remove_domain(HostType, Domain);
469 false ->
470
:-(
ok
471 end,
472
:-(
Acc.
473
474 -spec disco_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc().
475 disco_features(Acc = #{node := <<>>}) ->
476 67 mongoose_disco:add_features([?NS_FEATURE_MSGOFFLINE], Acc);
477 disco_features(Acc = #{node := ?NS_FEATURE_MSGOFFLINE}) ->
478 %% override all lesser features...
479
:-(
Acc#{result := []};
480 disco_features(Acc) ->
481
:-(
Acc.
482
483 -spec determine_amp_strategy(amp_strategy(), jid:jid(), jid:jid(), exml:element(), amp_event()) ->
484 amp_strategy().
485 determine_amp_strategy(Strategy = #amp_strategy{deliver = [none]},
486 _FromJID, ToJID, _Packet, initial_check) ->
487 64 case ejabberd_auth:does_user_exist(ToJID) of
488 32 true -> Strategy#amp_strategy{deliver = [stored, none]};
489 32 false -> Strategy
490 end;
491 determine_amp_strategy(Strategy, _, _, _, _) ->
492 160 Strategy.
493
494 -spec get_personal_data(gdpr:personal_data(), mongooseim:host_type(), jid:jid()) -> gdpr:personal_data().
495 get_personal_data(Acc, HostType, #jid{} = JID) ->
496 33 {ok, Messages} = mod_offline_backend:fetch_messages(HostType, JID),
497 33 [ {offline, ["timestamp", "from", "to", "packet"],
498 offline_messages_to_gdpr_format(Messages)} | Acc].
499
500 offline_messages_to_gdpr_format(MsgList) ->
501 33 [offline_msg_to_gdpr_format(Msg) || Msg <- MsgList].
502
503 offline_msg_to_gdpr_format(#offline_msg{timestamp = TimeStamp, from = From,
504 to = To, packet = Packet}) ->
505 3 SystemTime = erlang:convert_time_unit(TimeStamp, microsecond, second),
506 3 UTCTime = calendar:system_time_to_rfc3339(SystemTime, [{offset, "Z"}]),
507 3 UTC = list_to_binary(UTCTime),
508 3 {UTC, jid:to_binary(From), jid:to_binary(jid:to_bare(To)), exml:to_binary(Packet)}.
509
510 skip_expired_messages(TimeStamp, Rs) ->
511 350 [R || R <- Rs, not is_expired_message(TimeStamp, R)].
512
513 is_expired_message(_TimeStamp, #offline_msg{expire=never}) ->
514 134 false;
515 is_expired_message(TimeStamp, #offline_msg{expire=ExpireTimeStamp}) ->
516 6 ExpireTimeStamp < TimeStamp.
517
518 compose_offline_message(#offline_msg{from = From, to = To, permanent_fields = PermanentFields},
519 Packet, Acc0) ->
520 135 Acc1 = mongoose_acc:set_permanent(PermanentFields, Acc0),
521 135 Acc = mongoose_acc:update_stanza(#{element => Packet, from_jid => From, to_jid => To}, Acc1),
522 135 {route, From, To, Acc}.
523
524 resend_offline_message_packet(LServer,
525 #offline_msg{timestamp=TimeStamp, packet = Packet}) ->
526 135 add_timestamp(TimeStamp, LServer, Packet).
527
528 add_timestamp(undefined, _LServer, Packet) ->
529
:-(
Packet;
530 add_timestamp(TimeStamp, LServer, Packet) ->
531 135 TimeStampXML = timestamp_xml(LServer, TimeStamp),
532 135 xml:append_subtags(Packet, [TimeStampXML]).
533
534 timestamp_xml(LServer, Time) ->
535 135 FromJID = jid:make_noprep(<<>>, LServer, <<>>),
536 135 TS = calendar:system_time_to_rfc3339(Time, [{offset, "Z"}, {unit, microsecond}]),
537 135 jlib:timestamp_to_xml(TS, FromJID, <<"Offline Storage">>).
538
539 -spec remove_expired_messages(mongooseim:host_type(), jid:lserver()) -> {ok, msg_count()} | {error, any()}.
540 remove_expired_messages(HostType, LServer) ->
541 1 Result = mod_offline_backend:remove_expired_messages(HostType, LServer),
542 1 mongoose_lib:log_if_backend_error(Result, ?MODULE, ?LINE, [HostType]),
543 1 Result.
544
545 -spec remove_old_messages(mongooseim:host_type(), jid:lserver(), non_neg_integer()) ->
546 {ok, msg_count()} | {error, any()}.
547 remove_old_messages(HostType, LServer, HowManyDays) ->
548 1 Timestamp = fallback_timestamp(HowManyDays, erlang:system_time(microsecond)),
549 1 Result = mod_offline_backend:remove_old_messages(HostType, LServer, Timestamp),
550 1 mongoose_lib:log_if_backend_error(Result, ?MODULE, ?LINE, [HostType, Timestamp]),
551 1 Result.
552
553 %% Warn senders that their messages have been discarded:
554 discard_warn_sender(Msgs) ->
555 9 lists:foreach(
556 fun({Acc, #offline_msg{from=From, to=To, packet=Packet}}) ->
557 9 ErrText = <<"Your contact offline message queue is full."
558 " The message has been discarded.">>,
559 9 Lang = exml_query:attr(Packet, <<"xml:lang">>, <<>>),
560 9 amp_failed_event(Acc),
561 9 {Acc1, Err} = jlib:make_error_reply(
562 Acc, Packet, mongoose_xmpp_errors:resource_constraint(Lang, ErrText)),
563 9 ejabberd_router:route(To, From, Acc1, Err)
564 end, Msgs).
565
566 fallback_timestamp(HowManyDays, TS_MicroSeconds) ->
567 1 HowManySeconds = HowManyDays * 86400,
568 1 HowManyMicroSeconds = erlang:convert_time_unit(HowManySeconds, second, microsecond),
569 1 TS_MicroSeconds - HowManyMicroSeconds.
570
571 config_metrics(HostType) ->
572
:-(
OptsToReport = [{backend, mnesia}], %list of tuples {option, default_value}
573
:-(
mongoose_module_metrics:opts_for_module(HostType, ?MODULE, OptsToReport).
Line Hits Source