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