./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.1"}]).
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, hooks/1, config_spec/0, supported_features/0]).
38
39 %% Hook handlers
40 -export([inspect_packet/3,
41 pop_offline_messages/3,
42 remove_user/3,
43 remove_domain/3,
44 disco_features/3,
45 determine_amp_strategy/3,
46 amp_failed_event/3,
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 behaviour_info/1, code_change/3, handle_call/3, handle_cast/2,
67 handle_info/2, init/1, start_link/3, terminate/2
68 ]).
69
70 -include("mongoose.hrl").
71 -include("jlib.hrl").
72 -include("amp.hrl").
73 -include("mod_offline.hrl").
74 -include("mongoose_config_spec.hrl").
75
76 %% default value for the maximum number of user messages
77 -define(MAX_USER_MESSAGES, infinity).
78
79 -type msg() :: #offline_msg{us :: {jid:luser(), jid:lserver()},
80 timestamp :: integer(),
81 expire :: integer() | never,
82 from :: jid:jid(),
83 to :: jid:jid(),
84 packet :: exml:element()}.
85
86 -export_type([msg/0]).
87
88 -type poppers() :: monitored_map:t({jid:luser(), jid:lserver()}, pid()).
89
90 -record(state, {host_type :: mongooseim:host_type(),
91 access_max_user_messages :: atom(),
92 message_poppers = monitored_map:new() :: poppers()
93 }).
94
95 -type state() :: #state{}.
96
97 %% Types used in backend callbacks
98 -type msg_count() :: non_neg_integer().
99 -type timestamp() :: integer().
100
101 -export_type([msg_count/0, timestamp/0]).
102
103 %% gen_mod callbacks
104 %% ------------------------------------------------------------------
105
106 -spec start(mongooseim:host_type(), gen_mod:module_opts()) -> ok.
107 start(HostType, #{access_max_user_messages := AccessMaxOfflineMsgs} = Opts) ->
108 17 mod_offline_backend:init(HostType, Opts),
109 17 start_worker(HostType, AccessMaxOfflineMsgs),
110 17 ok.
111
112 -spec stop(mongooseim:host_type()) -> ok.
113 stop(Host) ->
114 17 stop_worker(Host),
115 17 ok.
116
117 -spec config_spec() -> mongoose_config_spec:config_section().
118 config_spec() ->
119 186 #section{
120 items = #{<<"access_max_user_messages">> => #option{type = atom,
121 validate = access_rule},
122 <<"backend">> => #option{type = atom,
123 validate = {module, mod_offline}},
124 <<"store_groupchat_messages">> => #option{type = boolean}
125 },
126 defaults = #{<<"access_max_user_messages">> => max_user_offline_messages,
127 <<"store_groupchat_messages">> => false,
128 <<"backend">> => mnesia
129 }
130 }.
131
132 19 supported_features() -> [dynamic_domains].
133
134 -spec hooks(mongooseim:host_type()) -> gen_hook:hook_list().
135 hooks(HostType) ->
136 34 DefaultHooks = [
137 {offline_message_hook, HostType, fun ?MODULE:inspect_packet/3, #{}, 50},
138 {resend_offline_messages_hook, HostType, fun ?MODULE:pop_offline_messages/3, #{}, 50},
139 {remove_user, HostType, fun ?MODULE:remove_user/3, #{}, 50},
140 {remove_domain, HostType, fun ?MODULE:remove_domain/3, #{}, 50},
141 {anonymous_purge_hook, HostType, fun ?MODULE:remove_user/3, #{}, 50},
142 {disco_sm_features, HostType, fun ?MODULE:disco_features/3, #{}, 50},
143 {disco_local_features, HostType, fun ?MODULE:disco_features/3, #{}, 50},
144 {amp_determine_strategy, HostType, fun ?MODULE:determine_amp_strategy/3, #{}, 30},
145 {failed_to_store_message, HostType, fun ?MODULE:amp_failed_event/3, #{}, 30},
146 {get_personal_data, HostType, fun ?MODULE:get_personal_data/3, #{}, 50}
147 ],
148 34 case gen_mod:get_module_opt(HostType, ?MODULE, store_groupchat_messages) of
149 true ->
150 4 GroupChatHook = {offline_groupchat_message_hook,
151 HostType, fun ?MODULE:inspect_packet/3, #{}, 50},
152 4 [GroupChatHook | DefaultHooks];
153 30 _ -> DefaultHooks
154 end.
155
156 %% Server side functions
157 %% ------------------------------------------------------------------
158
159 -spec amp_failed_event(Acc, Params, Extra) -> {ok, Acc} when
160 Acc :: mongoose_acc:t(),
161 Params :: map(),
162 Extra :: gen_hook:extra().
163 amp_failed_event(Acc, _Params, _Extra) ->
164 8 {ok, mod_amp:check_packet(Acc, offline_failed)}.
165
166 handle_offline_msg(HostType, Acc, #offline_msg{us=US} = Msg, AccessMaxOfflineMsgs) ->
167 85 {LUser, LServer} = US,
168 85 Msgs = receive_all(US, [{Acc, Msg}]),
169 85 MaxOfflineMsgs = get_max_user_messages(HostType, AccessMaxOfflineMsgs, LUser, LServer),
170 85 Len = length(Msgs),
171 85 case is_message_count_threshold_reached(HostType, MaxOfflineMsgs, LUser, LServer, Len) of
172 false ->
173 84 write_messages(HostType, LUser, LServer, Msgs);
174 true ->
175 1 discard_warn_sender(Msgs)
176 end.
177
178 write_messages(HostType, LUser, LServer, Msgs) ->
179 84 MsgsWithoutAcc = [Msg || {_Acc, Msg} <- Msgs],
180 84 case mod_offline_backend:write_messages(HostType, LUser, LServer, MsgsWithoutAcc) of
181 ok ->
182 76 [mod_amp:check_packet(Acc, archived) || {Acc, _Msg} <- Msgs],
183 76 ok;
184 {error, Reason} ->
185 8 ?LOG_ERROR(#{what => offline_write_failed,
186 text => <<"Failed to write offline messages">>,
187 reason => Reason,
188
:-(
user => LUser, server => LServer, msgs => Msgs}),
189 8 discard_warn_sender(Msgs)
190 end.
191
192 -spec is_message_count_threshold_reached(mongooseim:host_type(), integer() | infinity,
193 jid:luser(), jid:lserver(), integer()) ->
194 boolean().
195 is_message_count_threshold_reached(_HostType, infinity, _LUser, _LServer, _Len) ->
196
:-(
false;
197 is_message_count_threshold_reached(_HostType, MaxOfflineMsgs, _LUser, _LServer, Len)
198 when Len > MaxOfflineMsgs ->
199
:-(
true;
200 is_message_count_threshold_reached(HostType, MaxOfflineMsgs, LUser, LServer, Len) ->
201 %% Only count messages if needed.
202 85 MaxArchivedMsg = MaxOfflineMsgs - Len,
203 %% Maybe do not need to count all messages in archive
204 85 MaxArchivedMsg < mod_offline_backend:count_offline_messages(HostType, LUser, LServer,
205 MaxArchivedMsg + 1).
206
207
208 get_max_user_messages(HostType, AccessRule, LUser, LServer) ->
209 85 case acl:match_rule(HostType, LServer, AccessRule, jid:make_noprep(LUser, LServer, <<>>)) of
210 85 Max when is_integer(Max) -> Max;
211
:-(
infinity -> infinity;
212
:-(
_ -> ?MAX_USER_MESSAGES
213 end.
214
215 receive_all(US, Msgs) ->
216 170 receive
217 {_Acc, #offline_msg{us=US}} = Msg ->
218 85 receive_all(US, [Msg | Msgs])
219 after 0 ->
220 85 Msgs
221 end.
222
223 %% Supervision
224 %% ------------------------------------------------------------------
225
226 start_worker(HostType, AccessMaxOfflineMsgs) ->
227 17 Proc = srv_name(HostType),
228 17 ChildSpec =
229 {Proc,
230 {?MODULE, start_link, [Proc, HostType, AccessMaxOfflineMsgs]},
231 permanent, 5000, worker, [?MODULE]},
232 17 ejabberd_sup:start_child(ChildSpec).
233
234 stop_worker(HostType) ->
235 17 Proc = srv_name(HostType),
236 17 ejabberd_sup:stop_child(Proc).
237
238 start_link(Name, HostType, AccessMaxOfflineMsgs) ->
239 17 gen_server:start_link({local, Name}, ?MODULE, [HostType, AccessMaxOfflineMsgs], []).
240
241 srv_name() ->
242 858 mod_offline.
243
244 srv_name(HostType) ->
245 858 gen_mod:get_module_proc(HostType, srv_name()).
246
247 %%====================================================================
248 %% gen_server callbacks
249 %%====================================================================
250
251 -spec init(list()) -> {ok, state()}.
252 init([HostType, AccessMaxOfflineMsgs]) ->
253 17 {ok, #state{host_type = HostType,
254 access_max_user_messages = AccessMaxOfflineMsgs}}.
255
256 -spec handle_call(Request :: any(), {pid(), any()}, state()) -> {reply, Result, state()}
257 when Result :: ok | {ok, [msg()]} | {error, any()}.
258 handle_call({pop_offline_messages, JID}, {Pid, _}, State = #state{host_type = HostType}) ->
259 654 Result = mod_offline_backend:pop_messages(HostType, JID),
260 654 NewPoppers = monitored_map:put(jid:to_lus(JID), Pid, Pid, State#state.message_poppers),
261 654 {reply, Result, State#state{message_poppers = NewPoppers}};
262 handle_call(Request, From, State) ->
263
:-(
?UNEXPECTED_CALL(Request, From),
264
:-(
{reply, ok, State}.
265
266 -spec handle_cast(any(), state()) -> {noreply, state()}.
267 handle_cast(Msg, State) ->
268
:-(
?UNEXPECTED_CAST(Msg),
269
:-(
{noreply, State}.
270
271 -spec handle_info(any(), state()) -> {noreply, state()}.
272 handle_info({'DOWN', _MonitorRef, _Type, _Object, _Info} = Msg, State) ->
273 639 NewPoppers = monitored_map:handle_info(Msg, State#state.message_poppers),
274 639 {noreply, State#state{message_poppers = NewPoppers}};
275 handle_info({Acc, Msg = #offline_msg{us = US}},
276 State = #state{host_type = HostType,
277 access_max_user_messages = AccessMaxOfflineMsgs}) ->
278 85 handle_offline_msg(HostType, Acc, Msg, AccessMaxOfflineMsgs),
279 85 case monitored_map:find(US, State#state.message_poppers) of
280 {ok, Pid} ->
281 9 Pid ! new_offline_messages;
282 76 error -> ok
283 end,
284 85 {noreply, State};
285 handle_info(Msg, State) ->
286
:-(
?UNEXPECTED_INFO(Msg),
287
:-(
{noreply, State}.
288
289 -spec terminate(any(), state()) -> ok.
290 terminate(_Reason, _State) ->
291
:-(
ok.
292
293 -spec code_change(any(), state(), any()) -> {ok, state()}.
294 code_change(_OldVsn, State, _Extra) ->
295
:-(
{ok, State}.
296
297 %% Handlers
298 %% ------------------------------------------------------------------
299
300 %% This function should be called only from a hook
301 %% Calling it directly is dangerous and may store unwanted messages
302 %% in the offline storage (e.g. messages of type error)
303 -spec inspect_packet(Acc, Params, Extra) -> {ok | stop, Acc} when
304 Acc :: mongoose_acc:t(),
305 Params :: map(),
306 Extra :: gen_hook:extra().
307 inspect_packet(Acc, #{from := From, to := To, packet := Packet}, _Extra) ->
308 170 case check_event_chatstates(Acc, From, To, Packet) of
309 true ->
310 170 Acc1 = store_packet(Acc, From, To, Packet),
311 170 {stop, Acc1};
312 false ->
313
:-(
{ok, 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 170 TimeStamp = get_or_build_timestamp_from_packet(Packet),
320 170 Expire = find_x_expire(TimeStamp, Els),
321 170 HostType = mongoose_acc:host_type(Acc),
322 170 Pid = srv_name(HostType),
323 170 PermanentFields = mongoose_acc:get_permanent_fields(Acc),
324 170 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 170 Pid ! {Acc, Msg},
332 170 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 170 case exml_query:path(Packet, [{element, <<"delay">>}, {attr, <<"stamp">>}]) of
337 undefined ->
338 170 erlang:system_time(microsecond);
339 Stamp ->
340
:-(
try
341
:-(
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 170 #xmlel{children = Els} = Packet,
350 170 case find_x_event_chatstates(Els, {false, false, false}) of
351 %% There wasn't any x:event or chatstates subelements
352 {false, false, _} ->
353 170 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 170 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 176 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 176 _ -> find_x_event_chatstates(Els, {A, B, true})
403 end.
404
405 find_x_expire(_, []) ->
406 168 never;
407 find_x_expire(TimeStamp, [#xmlcdata{} | Els]) ->
408
:-(
find_x_expire(TimeStamp, Els);
409 find_x_expire(TimeStamp, [El | Els]) ->
410 174 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 172 find_x_expire(TimeStamp, Els)
424 end.
425
426 -spec pop_offline_messages(Acc, Params, Extra) -> {ok, Acc} when
427 Acc :: mongoose_acc:t(),
428 Params :: map(),
429 Extra :: gen_hook:extra().
430 pop_offline_messages(Acc, #{jid := JID}, _Extra) ->
431 654 {ok, mongoose_acc:append(offline, messages, offline_messages(Acc, JID), Acc)}.
432
433 -spec offline_messages(mongoose_acc:t(), jid:jid()) ->
434 [{route, jid:jid(), jid:jid(), mongoose_acc:t()}].
435 offline_messages(Acc, #jid{lserver = LServer} = JID) ->
436 654 HostType = mongoose_acc:host_type(Acc),
437 654 case pop_messages(HostType, JID) of
438 {ok, Rs} ->
439 654 lists:map(fun(R) ->
440 127 Packet = resend_offline_message_packet(LServer, R),
441 127 compose_offline_message(R, Packet, Acc)
442 end, Rs);
443 {error, Reason} ->
444
:-(
?LOG_WARNING(#{what => offline_pop_failed, reason => Reason, acc => Acc}),
445
:-(
[]
446 end.
447
448 -spec pop_messages(mongooseim:host_type(), jid:jid()) -> {ok, [msg()]} | {error, any()}.
449 pop_messages(HostType, JID) ->
450 654 case gen_server:call(srv_name(HostType), {pop_offline_messages, jid:to_bare(JID)}) of
451 {ok, RsAll} ->
452 654 TimeStamp = erlang:system_time(microsecond),
453 654 Rs = skip_expired_messages(TimeStamp, lists:keysort(#offline_msg.timestamp, RsAll)),
454 654 {ok, Rs};
455 Other ->
456
:-(
Other
457 end.
458
459 -spec remove_user(Acc, Params, Extra) -> {ok, Acc} when
460 Acc :: mongoose_acc:t(),
461 Params :: map(),
462 Extra :: gen_hook:extra().
463 remove_user(Acc, #{jid := #jid{luser = LUser, lserver = LServer}}, #{host_type := HostType}) ->
464 416 mod_offline_backend:remove_user(HostType, LUser, LServer),
465 416 {ok, Acc}.
466
467 -spec remove_domain(Acc, Params, Extra) -> {ok, Acc} when
468 Acc :: mongoose_domain_api:remove_domain_acc(),
469 Params :: map(),
470 Extra :: gen_hook:extra().
471 remove_domain(Acc, #{domain := Domain}, #{host_type := HostType}) ->
472 1 case mongoose_lib:is_exported(mod_offline_backend, remove_domain, 2) of
473 true ->
474 1 mod_offline_backend:remove_domain(HostType, Domain);
475 false ->
476
:-(
ok
477 end,
478 1 {ok, Acc}.
479
480 -spec disco_features(Acc, Params, Extra) -> {ok, Acc} when
481 Acc :: mongoose_disco:feature_acc(),
482 Params :: map(),
483 Extra :: gen_hook:extra().
484 disco_features(Acc = #{node := <<>>}, _Params, _Extra) ->
485 3 {ok, mongoose_disco:add_features([?NS_FEATURE_MSGOFFLINE], Acc)};
486 disco_features(Acc = #{node := ?NS_FEATURE_MSGOFFLINE}, _Params, _Extra) ->
487 %% override all lesser features...
488
:-(
{ok, Acc#{result := []}};
489 disco_features(Acc, _Params, _Extra) ->
490
:-(
{ok, Acc}.
491
492 -spec determine_amp_strategy(Acc, Params, Extra) -> {ok, Acc} when
493 Acc :: mod_amp:amp_strategy(),
494 Params :: map(),
495 Extra :: gen_hook:extra().
496 determine_amp_strategy(Strategy = #amp_strategy{deliver = [none]}, #{to := ToJID, event := initial_check}, _Params) ->
497 64 case ejabberd_auth:does_user_exist(ToJID) of
498 32 true -> {ok, Strategy#amp_strategy{deliver = [stored, none]}};
499 32 false -> {ok, Strategy}
500 end;
501 determine_amp_strategy(Strategy, _Params, _Extra) ->
502 160 {ok, Strategy}.
503
504 -spec get_personal_data(Acc, Params, Extra) -> {ok | stop, Acc} when
505 Acc :: gdpr:personal_data(),
506 Params :: #{jid := jid:jid()},
507 Extra :: gen_hook:extra().
508 get_personal_data(Acc, #{jid := JID}, #{host_type := HostType}) ->
509 20 {ok, Messages} = mod_offline_backend:fetch_messages(HostType, JID),
510 20 {ok, [{offline, ["timestamp", "from", "to", "packet"],
511 offline_messages_to_gdpr_format(Messages)} | Acc]}.
512
513 offline_messages_to_gdpr_format(MsgList) ->
514 20 [offline_msg_to_gdpr_format(Msg) || Msg <- MsgList].
515
516 offline_msg_to_gdpr_format(#offline_msg{timestamp = TimeStamp, from = From,
517 to = To, packet = Packet}) ->
518 3 SystemTime = erlang:convert_time_unit(TimeStamp, microsecond, second),
519 3 UTCTime = calendar:system_time_to_rfc3339(SystemTime, [{offset, "Z"}]),
520 3 UTC = list_to_binary(UTCTime),
521 3 {UTC, jid:to_binary(From), jid:to_bare_binary(To), exml:to_binary(Packet)}.
522
523 skip_expired_messages(TimeStamp, Rs) ->
524 654 [R || R <- Rs, not is_expired_message(TimeStamp, R)].
525
526 is_expired_message(_TimeStamp, #offline_msg{expire=never}) ->
527 127 false;
528 is_expired_message(TimeStamp, #offline_msg{expire=ExpireTimeStamp}) ->
529
:-(
ExpireTimeStamp < TimeStamp.
530
531 compose_offline_message(#offline_msg{from = From, to = To, permanent_fields = PermanentFields},
532 Packet, Acc0) ->
533 127 Acc1 = mongoose_acc:set_permanent(PermanentFields, Acc0),
534 127 Acc = mongoose_acc:update_stanza(#{element => Packet, from_jid => From, to_jid => To}, Acc1),
535 127 {route, From, To, Acc}.
536
537 resend_offline_message_packet(LServer,
538 #offline_msg{timestamp=TimeStamp, packet = Packet}) ->
539 127 add_timestamp(TimeStamp, LServer, Packet).
540
541 add_timestamp(undefined, _LServer, Packet) ->
542
:-(
Packet;
543 add_timestamp(TimeStamp, LServer, Packet) ->
544 127 TimeStampXML = timestamp_xml(LServer, TimeStamp),
545 127 xml:append_subtags(Packet, [TimeStampXML]).
546
547 timestamp_xml(LServer, Time) ->
548 127 FromJID = jid:make_noprep(<<>>, LServer, <<>>),
549 127 TS = calendar:system_time_to_rfc3339(Time, [{offset, "Z"}, {unit, microsecond}]),
550 127 jlib:timestamp_to_xml(TS, FromJID, <<"Offline Storage">>).
551
552 -spec remove_expired_messages(mongooseim:host_type(), jid:lserver()) -> {ok, msg_count()} | {error, any()}.
553 remove_expired_messages(HostType, LServer) ->
554 9 Result = mod_offline_backend:remove_expired_messages(HostType, LServer),
555 9 mongoose_lib:log_if_backend_error(Result, ?MODULE, ?LINE, [HostType]),
556 9 Result.
557
558 -spec remove_old_messages(mongooseim:host_type(), jid:lserver(), non_neg_integer()) ->
559 {ok, msg_count()} | {error, any()}.
560 remove_old_messages(HostType, LServer, HowManyDays) ->
561 9 Timestamp = fallback_timestamp(HowManyDays, erlang:system_time(microsecond)),
562 9 Result = mod_offline_backend:remove_old_messages(HostType, LServer, Timestamp),
563 9 mongoose_lib:log_if_backend_error(Result, ?MODULE, ?LINE, [HostType, Timestamp]),
564 9 Result.
565
566 %% Warn senders that their messages have been discarded:
567 discard_warn_sender(Msgs) ->
568 9 lists:foreach(
569 fun({Acc, #offline_msg{from=From, to=To, packet=Packet}}) ->
570 9 ErrText = <<"Your contact offline message queue is full."
571 " The message has been discarded.">>,
572 9 Lang = exml_query:attr(Packet, <<"xml:lang">>, <<>>),
573 9 mod_amp:check_packet(Acc, offline_failed),
574 9 {Acc1, Err} = jlib:make_error_reply(
575 Acc, Packet, mongoose_xmpp_errors:resource_constraint(Lang, ErrText)),
576 9 ejabberd_router:route(To, From, Acc1, Err)
577 end, Msgs).
578
579 fallback_timestamp(HowManyDays, TS_MicroSeconds) ->
580 9 HowManySeconds = HowManyDays * 86400,
581 9 HowManyMicroSeconds = erlang:convert_time_unit(HowManySeconds, second, microsecond),
582 9 TS_MicroSeconds - HowManyMicroSeconds.
583
584 config_metrics(HostType) ->
585 24 mongoose_module_metrics:opts_for_module(HostType, ?MODULE, [backend]).
Line Hits Source