./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, remove_unused_backend_opts/1]).
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 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
109 start(HostType, #{access_max_user_messages := AccessMaxOfflineMsgs} = Opts) ->
110 15 mod_offline_backend:init(HostType, Opts),
111 15 start_worker(HostType, AccessMaxOfflineMsgs),
112 15 ejabberd_hooks:add(hooks(HostType)),
113 15 ok.
114
115 -spec stop(mongooseim:host_type()) -> ok.
116 stop(Host) ->
117 15 ejabberd_hooks:delete(hooks(Host)),
118 15 stop_worker(Host),
119 15 ok.
120
121 -spec config_spec() -> mongoose_config_spec:config_section().
122 config_spec() ->
123 152 #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 <<"store_groupchat_messages">> => #option{type = boolean},
129 <<"riak">> => riak_config_spec()
130 },
131 defaults = #{<<"access_max_user_messages">> => max_user_offline_messages,
132 <<"store_groupchat_messages">> => false,
133 <<"backend">> => mnesia
134 },
135 format_items = map,
136 process = fun ?MODULE:remove_unused_backend_opts/1
137 }.
138
139 riak_config_spec() ->
140 152 #section{
141 items = #{<<"bucket_type">> => #option{type = binary,
142 validate = non_empty}},
143 defaults = #{<<"bucket_type">> => <<"offline">>},
144 format_items = map,
145 include = always
146 }.
147
148 -spec remove_unused_backend_opts(gen_mod:module_opts()) -> gen_mod:module_opts().
149
:-(
remove_unused_backend_opts(Opts = #{backend := riak}) -> Opts;
150
:-(
remove_unused_backend_opts(Opts) -> maps:remove(riak, Opts).
151
152 17 supported_features() -> [dynamic_domains].
153
154 hooks(HostType) ->
155 30 DefaultHooks = [
156 {offline_message_hook, HostType, ?MODULE, inspect_packet, 50},
157 {resend_offline_messages_hook, HostType, ?MODULE, pop_offline_messages, 50},
158 {remove_user, HostType, ?MODULE, remove_user, 50},
159 {remove_domain, HostType, ?MODULE, remove_domain, 50},
160 {anonymous_purge_hook, HostType, ?MODULE, remove_user, 50},
161 {disco_sm_features, HostType, ?MODULE, disco_features, 50},
162 {disco_local_features, HostType, ?MODULE, disco_features, 50},
163 {amp_determine_strategy, HostType, ?MODULE, determine_amp_strategy, 30},
164 {failed_to_store_message, HostType, ?MODULE, amp_failed_event, 30},
165 {get_personal_data, HostType, ?MODULE, get_personal_data, 50}
166 ],
167 30 case gen_mod:get_module_opt(HostType, ?MODULE, store_groupchat_messages) of
168 true ->
169 4 GroupChatHook = {offline_groupchat_message_hook,
170 HostType, ?MODULE, inspect_packet, 50},
171 4 [GroupChatHook | DefaultHooks];
172 26 _ -> DefaultHooks
173 end.
174
175
176
177 %% Server side functions
178 %% ------------------------------------------------------------------
179
180 amp_failed_event(Acc) ->
181 17 mod_amp:check_packet(Acc, offline_failed).
182
183 handle_offline_msg(HostType, Acc, #offline_msg{us=US} = Msg, AccessMaxOfflineMsgs) ->
184 125 {LUser, LServer} = US,
185 125 Msgs = receive_all(US, [{Acc, Msg}]),
186 125 MaxOfflineMsgs = get_max_user_messages(HostType, AccessMaxOfflineMsgs, LUser, LServer),
187 125 Len = length(Msgs),
188 125 case is_message_count_threshold_reached(HostType, MaxOfflineMsgs, LUser, LServer, Len) of
189 false ->
190 124 write_messages(HostType, LUser, LServer, Msgs);
191 true ->
192 1 discard_warn_sender(Msgs)
193 end.
194
195 write_messages(HostType, LUser, LServer, Msgs) ->
196 124 MsgsWithoutAcc = [Msg || {_Acc, Msg} <- Msgs],
197 124 case mod_offline_backend:write_messages(HostType, LUser, LServer, MsgsWithoutAcc) of
198 ok ->
199 116 [mod_amp:check_packet(Acc, archived) || {Acc, _Msg} <- Msgs],
200 116 ok;
201 {error, Reason} ->
202 8 ?LOG_ERROR(#{what => offline_write_failed,
203 text => <<"Failed to write offline messages">>,
204 reason => Reason,
205
:-(
user => LUser, server => LServer, msgs => Msgs}),
206 8 discard_warn_sender(Msgs)
207 end.
208
209 -spec is_message_count_threshold_reached(mongooseim:host_type(), integer() | infinity,
210 jid:luser(), jid:lserver(), integer()) ->
211 boolean().
212 is_message_count_threshold_reached(_HostType, infinity, _LUser, _LServer, _Len) ->
213
:-(
false;
214 is_message_count_threshold_reached(_HostType, MaxOfflineMsgs, _LUser, _LServer, Len)
215 when Len > MaxOfflineMsgs ->
216
:-(
true;
217 is_message_count_threshold_reached(HostType, MaxOfflineMsgs, LUser, LServer, Len) ->
218 %% Only count messages if needed.
219 125 MaxArchivedMsg = MaxOfflineMsgs - Len,
220 %% Maybe do not need to count all messages in archive
221 125 MaxArchivedMsg < mod_offline_backend:count_offline_messages(HostType, LUser, LServer,
222 MaxArchivedMsg + 1).
223
224
225 get_max_user_messages(HostType, AccessRule, LUser, LServer) ->
226 125 case acl:match_rule(HostType, LServer, AccessRule, jid:make_noprep(LUser, LServer, <<>>)) of
227 125 Max when is_integer(Max) -> Max;
228
:-(
infinity -> infinity;
229
:-(
_ -> ?MAX_USER_MESSAGES
230 end.
231
232 receive_all(US, Msgs) ->
233 183 receive
234 {_Acc, #offline_msg{us=US}} = Msg ->
235 58 receive_all(US, [Msg | Msgs])
236 after 0 ->
237 125 Msgs
238 end.
239
240 %% Supervision
241 %% ------------------------------------------------------------------
242
243 start_worker(HostType, AccessMaxOfflineMsgs) ->
244 15 Proc = srv_name(HostType),
245 15 ChildSpec =
246 {Proc,
247 {?MODULE, start_link, [Proc, HostType, AccessMaxOfflineMsgs]},
248 permanent, 5000, worker, [?MODULE]},
249 15 ejabberd_sup:start_child(ChildSpec).
250
251 stop_worker(HostType) ->
252 15 Proc = srv_name(HostType),
253 15 ejabberd_sup:stop_child(Proc).
254
255 start_link(Name, HostType, AccessMaxOfflineMsgs) ->
256 15 gen_server:start_link({local, Name}, ?MODULE, [HostType, AccessMaxOfflineMsgs], []).
257
258 srv_name() ->
259 830 mod_offline.
260
261 srv_name(HostType) ->
262 830 gen_mod:get_module_proc(HostType, srv_name()).
263
264 %%====================================================================
265 %% gen_server callbacks
266 %%====================================================================
267
268 -spec init(list()) -> {ok, state()}.
269 init([HostType, AccessMaxOfflineMsgs]) ->
270 15 {ok, #state{host_type = HostType,
271 access_max_user_messages = AccessMaxOfflineMsgs}}.
272
273 -spec handle_call(Request :: any(), {pid(), any()}, state()) -> {reply, Result, state()}
274 when Result :: ok | {ok, [msg()]} | {error, any()}.
275 handle_call({pop_offline_messages, JID}, {Pid, _}, State = #state{host_type = HostType}) ->
276 617 Result = mod_offline_backend:pop_messages(HostType, JID),
277 617 NewPoppers = monitored_map:put(jid:to_lus(JID), Pid, Pid, State#state.message_poppers),
278 617 {reply, Result, State#state{message_poppers = NewPoppers}};
279 handle_call(Request, From, State) ->
280
:-(
?UNEXPECTED_CALL(Request, From),
281
:-(
{reply, ok, State}.
282
283 -spec handle_cast(any(), state()) -> {noreply, state()}.
284 handle_cast(Msg, State) ->
285
:-(
?UNEXPECTED_CAST(Msg),
286
:-(
{noreply, State}.
287
288 -spec handle_info(any(), state()) -> {noreply, state()}.
289 handle_info({'DOWN', _MonitorRef, _Type, _Object, _Info} = Msg, State) ->
290 605 NewPoppers = monitored_map:handle_info(Msg, State#state.message_poppers),
291 605 {noreply, State#state{message_poppers = NewPoppers}};
292 handle_info({Acc, Msg = #offline_msg{us = US}},
293 State = #state{host_type = HostType,
294 access_max_user_messages = AccessMaxOfflineMsgs}) ->
295 125 handle_offline_msg(HostType, Acc, Msg, AccessMaxOfflineMsgs),
296 125 case monitored_map:find(US, State#state.message_poppers) of
297 {ok, Pid} ->
298 31 Pid ! new_offline_messages;
299 94 error -> ok
300 end,
301 125 {noreply, State};
302 handle_info(Msg, State) ->
303
:-(
?UNEXPECTED_INFO(Msg),
304
:-(
{noreply, State}.
305
306 -spec terminate(any(), state()) -> ok.
307 terminate(_Reason, _State) ->
308
:-(
ok.
309
310 -spec code_change(any(), state(), any()) -> {ok, state()}.
311 code_change(_OldVsn, State, _Extra) ->
312
:-(
{ok, State}.
313
314 %% Handlers
315 %% ------------------------------------------------------------------
316
317 %% This function should be called only from a hook
318 %% Calling it directly is dangerous and may store unwanted messages
319 %% in the offline storage (e.g. messages of type error)
320 -spec inspect_packet(mongoose_acc:t(), jid:jid(), jid:jid(), exml:element()) -> mongoose_acc:t().
321 inspect_packet(Acc, From, To, Packet) ->
322 183 case check_event_chatstates(Acc, From, To, Packet) of
323 true ->
324 183 Acc1 = store_packet(Acc, From, To, Packet),
325 183 {stop, Acc1};
326 false ->
327
:-(
Acc
328 end.
329
330 -spec store_packet(mongoose_acc:t(), jid:jid(), jid:jid(), exml:element()) -> mongoose_acc:t().
331 store_packet(Acc, From, To = #jid{luser = LUser, lserver = LServer},
332 Packet = #xmlel{children = Els}) ->
333 183 TimeStamp = get_or_build_timestamp_from_packet(Packet),
334 183 Expire = find_x_expire(TimeStamp, Els),
335 183 HostType = mongoose_acc:host_type(Acc),
336 183 Pid = srv_name(HostType),
337 183 PermanentFields = mongoose_acc:get_permanent_fields(Acc),
338 183 Msg = #offline_msg{us = {LUser, LServer},
339 timestamp = TimeStamp,
340 expire = Expire,
341 from = From,
342 to = To,
343 packet = jlib:remove_delay_tags(Packet),
344 permanent_fields = PermanentFields},
345 183 Pid ! {Acc, Msg},
346 183 mongoose_acc:set(offline, stored, true, Acc).
347
348 -spec get_or_build_timestamp_from_packet(exml:element()) -> integer().
349 get_or_build_timestamp_from_packet(Packet) ->
350 183 case exml_query:path(Packet, [{element, <<"delay">>}, {attr, <<"stamp">>}]) of
351 undefined ->
352 143 erlang:system_time(microsecond);
353 Stamp ->
354 40 try
355 40 calendar:rfc3339_to_system_time(binary_to_list(Stamp), [{unit, microsecond}])
356 catch
357
:-(
error:_Error -> erlang:system_time(microsecond)
358 end
359 end.
360
361 %% Check if the packet has any content about XEP-0022 or XEP-0085
362 check_event_chatstates(Acc, From, To, Packet) ->
363 183 #xmlel{children = Els} = Packet,
364 183 case find_x_event_chatstates(Els, {false, false, false}) of
365 %% There wasn't any x:event or chatstates subelements
366 {false, false, _} ->
367 183 true;
368 %% There a chatstates subelement and other stuff, but no x:event
369 {false, CEl, true} when CEl /= false ->
370
:-(
true;
371 %% There was only a subelement: a chatstates
372 {false, CEl, false} when CEl /= false ->
373 %% Don't allow offline storage
374
:-(
false;
375 %% There was an x:event element, and maybe also other stuff
376 {El, _, _} when El /= false ->
377
:-(
inspect_xevent(Acc, From, To, Packet, El)
378 end.
379
380 inspect_xevent(Acc, From, To, Packet, XEvent) ->
381
:-(
case exml_query:subelement(XEvent, <<"id">>) of
382 undefined ->
383
:-(
case exml_query:subelement(XEvent, <<"offline">>) of
384 undefined ->
385
:-(
true;
386 _ ->
387
:-(
ejabberd_router:route(To, From, Acc, patch_offline_message(Packet)),
388
:-(
true
389 end;
390 _ ->
391
:-(
false
392 end.
393
394 patch_offline_message(Packet) ->
395
:-(
ID = case exml_query:attr(Packet, <<"id">>, <<>>) of
396
:-(
<<"">> -> #xmlel{name = <<"id">>};
397
:-(
S -> #xmlel{name = <<"id">>, children = [#xmlcdata{content = S}]}
398 end,
399
:-(
Packet#xmlel{children = [x_elem(ID)]}.
400
401 x_elem(ID) ->
402
:-(
#xmlel{
403 name = <<"x">>,
404 attrs = [{<<"xmlns">>, ?NS_EVENT}],
405 children = [ID, #xmlel{name = <<"offline">>}]}.
406
407 %% Check if the packet has subelements about XEP-0022, XEP-0085 or other
408 find_x_event_chatstates([], Res) ->
409 183 Res;
410 find_x_event_chatstates([#xmlcdata{} | Els], Res) ->
411
:-(
find_x_event_chatstates(Els, Res);
412 find_x_event_chatstates([El | Els], {A, B, C}) ->
413 229 case exml_query:attr(El, <<"xmlns">>, <<>>) of
414
:-(
?NS_EVENT -> find_x_event_chatstates(Els, {El, B, C});
415
:-(
?NS_CHATSTATES -> find_x_event_chatstates(Els, {A, El, C});
416 229 _ -> find_x_event_chatstates(Els, {A, B, true})
417 end.
418
419 find_x_expire(_, []) ->
420 181 never;
421 find_x_expire(TimeStamp, [#xmlcdata{} | Els]) ->
422
:-(
find_x_expire(TimeStamp, Els);
423 find_x_expire(TimeStamp, [El | Els]) ->
424 227 case exml_query:attr(El, <<"xmlns">>, <<>>) of
425 ?NS_EXPIRE ->
426 2 Val = exml_query:attr(El, <<"seconds">>, <<>>),
427 2 try binary_to_integer(Val) of
428 Int when Int > 0 ->
429 2 ExpireMicroSeconds = erlang:convert_time_unit(Int, second, microsecond),
430 2 TimeStamp + ExpireMicroSeconds;
431 _ ->
432
:-(
never
433 catch
434
:-(
error:badarg -> never
435 end;
436 _ ->
437 225 find_x_expire(TimeStamp, Els)
438 end.
439
440 -spec pop_offline_messages(mongoose_acc:t(), jid:jid()) -> mongoose_acc:t().
441 pop_offline_messages(Acc, JID) ->
442 617 mongoose_acc:append(offline, messages, offline_messages(Acc, JID), Acc).
443
444 -spec offline_messages(mongoose_acc:t(), jid:jid()) ->
445 [{route, jid:jid(), jid:jid(), mongoose_acc:t()}].
446 offline_messages(Acc, #jid{lserver = LServer} = JID) ->
447 617 HostType = mongoose_acc:host_type(Acc),
448 617 case pop_messages(HostType, JID) of
449 {ok, Rs} ->
450 617 lists:map(fun(R) ->
451 132 Packet = resend_offline_message_packet(LServer, R),
452 132 compose_offline_message(R, Packet, Acc)
453 end, Rs);
454 {error, Reason} ->
455
:-(
?LOG_WARNING(#{what => offline_pop_failed, reason => Reason, acc => Acc}),
456
:-(
[]
457 end.
458
459 -spec pop_messages(mongooseim:host_type(), jid:jid()) -> {ok, [msg()]} | {error, any()}.
460 pop_messages(HostType, JID) ->
461 617 case gen_server:call(srv_name(HostType), {pop_offline_messages, jid:to_bare(JID)}) of
462 {ok, RsAll} ->
463 617 TimeStamp = erlang:system_time(microsecond),
464 617 Rs = skip_expired_messages(TimeStamp, lists:keysort(#offline_msg.timestamp, RsAll)),
465 617 {ok, Rs};
466 Other ->
467
:-(
Other
468 end.
469
470 -spec remove_user(mongoose_acc:t(), jid:luser(), jid:lserver()) -> mongoose_acc:t().
471 remove_user(Acc, LUser, LServer) ->
472 385 HostType = mongoose_acc:host_type(Acc),
473 385 mod_offline_backend:remove_user(HostType, LUser, LServer),
474 385 Acc.
475
476 -spec remove_domain(mongoose_hooks:simple_acc(),
477 mongooseim:host_type(), jid:lserver()) ->
478 mongoose_hooks:simple_acc().
479 remove_domain(Acc, HostType, Domain) ->
480 1 case backend_module:is_exported(mod_offline_backend, remove_domain, 2) of
481 true ->
482 1 mod_offline_backend:remove_domain(HostType, Domain);
483 false ->
484
:-(
ok
485 end,
486 1 Acc.
487
488 -spec disco_features(mongoose_disco:feature_acc()) -> mongoose_disco:feature_acc().
489 disco_features(Acc = #{node := <<>>}) ->
490 3 mongoose_disco:add_features([?NS_FEATURE_MSGOFFLINE], Acc);
491 disco_features(Acc = #{node := ?NS_FEATURE_MSGOFFLINE}) ->
492 %% override all lesser features...
493
:-(
Acc#{result := []};
494 disco_features(Acc) ->
495
:-(
Acc.
496
497 -spec determine_amp_strategy(amp_strategy(), jid:jid(), jid:jid(), exml:element(), amp_event()) ->
498 amp_strategy().
499 determine_amp_strategy(Strategy = #amp_strategy{deliver = [none]},
500 _FromJID, ToJID, _Packet, initial_check) ->
501 64 case ejabberd_auth:does_user_exist(ToJID) of
502 32 true -> Strategy#amp_strategy{deliver = [stored, none]};
503 32 false -> Strategy
504 end;
505 determine_amp_strategy(Strategy, _, _, _, _) ->
506 160 Strategy.
507
508 -spec get_personal_data(gdpr:personal_data(), mongooseim:host_type(), jid:jid()) -> gdpr:personal_data().
509 get_personal_data(Acc, HostType, #jid{} = JID) ->
510 20 {ok, Messages} = mod_offline_backend:fetch_messages(HostType, JID),
511 20 [ {offline, ["timestamp", "from", "to", "packet"],
512 offline_messages_to_gdpr_format(Messages)} | Acc].
513
514 offline_messages_to_gdpr_format(MsgList) ->
515 20 [offline_msg_to_gdpr_format(Msg) || Msg <- MsgList].
516
517 offline_msg_to_gdpr_format(#offline_msg{timestamp = TimeStamp, from = From,
518 to = To, packet = Packet}) ->
519 3 SystemTime = erlang:convert_time_unit(TimeStamp, microsecond, second),
520 3 UTCTime = calendar:system_time_to_rfc3339(SystemTime, [{offset, "Z"}]),
521 3 UTC = list_to_binary(UTCTime),
522 3 {UTC, jid:to_binary(From), jid:to_binary(jid:to_bare(To)), exml:to_binary(Packet)}.
523
524 skip_expired_messages(TimeStamp, Rs) ->
525 617 [R || R <- Rs, not is_expired_message(TimeStamp, R)].
526
527 is_expired_message(_TimeStamp, #offline_msg{expire=never}) ->
528 132 false;
529 is_expired_message(TimeStamp, #offline_msg{expire=ExpireTimeStamp}) ->
530 4 ExpireTimeStamp < TimeStamp.
531
532 compose_offline_message(#offline_msg{from = From, to = To, permanent_fields = PermanentFields},
533 Packet, Acc0) ->
534 132 Acc1 = mongoose_acc:set_permanent(PermanentFields, Acc0),
535 132 Acc = mongoose_acc:update_stanza(#{element => Packet, from_jid => From, to_jid => To}, Acc1),
536 132 {route, From, To, Acc}.
537
538 resend_offline_message_packet(LServer,
539 #offline_msg{timestamp=TimeStamp, packet = Packet}) ->
540 132 add_timestamp(TimeStamp, LServer, Packet).
541
542 add_timestamp(undefined, _LServer, Packet) ->
543
:-(
Packet;
544 add_timestamp(TimeStamp, LServer, Packet) ->
545 132 TimeStampXML = timestamp_xml(LServer, TimeStamp),
546 132 xml:append_subtags(Packet, [TimeStampXML]).
547
548 timestamp_xml(LServer, Time) ->
549 132 FromJID = jid:make_noprep(<<>>, LServer, <<>>),
550 132 TS = calendar:system_time_to_rfc3339(Time, [{offset, "Z"}, {unit, microsecond}]),
551 132 jlib:timestamp_to_xml(TS, FromJID, <<"Offline Storage">>).
552
553 -spec remove_expired_messages(mongooseim:host_type(), jid:lserver()) -> {ok, msg_count()} | {error, any()}.
554 remove_expired_messages(HostType, LServer) ->
555 1 Result = mod_offline_backend:remove_expired_messages(HostType, LServer),
556 1 mongoose_lib:log_if_backend_error(Result, ?MODULE, ?LINE, [HostType]),
557 1 Result.
558
559 -spec remove_old_messages(mongooseim:host_type(), jid:lserver(), non_neg_integer()) ->
560 {ok, msg_count()} | {error, any()}.
561 remove_old_messages(HostType, LServer, HowManyDays) ->
562 1 Timestamp = fallback_timestamp(HowManyDays, erlang:system_time(microsecond)),
563 1 Result = mod_offline_backend:remove_old_messages(HostType, LServer, Timestamp),
564 1 mongoose_lib:log_if_backend_error(Result, ?MODULE, ?LINE, [HostType, Timestamp]),
565 1 Result.
566
567 %% Warn senders that their messages have been discarded:
568 discard_warn_sender(Msgs) ->
569 9 lists:foreach(
570 fun({Acc, #offline_msg{from=From, to=To, packet=Packet}}) ->
571 9 ErrText = <<"Your contact offline message queue is full."
572 " The message has been discarded.">>,
573 9 Lang = exml_query:attr(Packet, <<"xml:lang">>, <<>>),
574 9 amp_failed_event(Acc),
575 9 {Acc1, Err} = jlib:make_error_reply(
576 Acc, Packet, mongoose_xmpp_errors:resource_constraint(Lang, ErrText)),
577 9 ejabberd_router:route(To, From, Acc1, Err)
578 end, Msgs).
579
580 fallback_timestamp(HowManyDays, TS_MicroSeconds) ->
581 1 HowManySeconds = HowManyDays * 86400,
582 1 HowManyMicroSeconds = erlang:convert_time_unit(HowManySeconds, second, microsecond),
583 1 TS_MicroSeconds - HowManyMicroSeconds.
584
585 config_metrics(HostType) ->
586 30 mongoose_module_metrics:opts_for_module(HostType, ?MODULE, [backend]).
Line Hits Source