./ct_report/coverage/mod_offline_chatmarkers.COVER.html

1 %%%----------------------------------------------------------------------------
2 %%% @copyright (C) 2020, Erlang Solutions Ltd.
3 %%% @doc
4 %%% This module optimizes offline storage for chat markers in the next way:
5 %%%
6 %%% 1) It filters out chat marker packets processed by mod_smart_markers:
7 %%%
8 %%% * These packets can be identified by the extra permanent Acc
9 %%% timestamp field added by mod_smart_markers.
10 %%%
11 %%% * These packets are not going to mod_offline (notice the
12 %%% difference in priorities for the offline_message_hook handlers)
13 %%%
14 %%% * The information about these chat markers is stored in DB,
15 %%% timestamp added by mod_smart_markers is important here!
16 %%%
17 %%% 2) After all the offline messages are inserted by mod_offline (notice
18 %%% the difference in priorities for the resend_offline_messages_hook
19 %%% handlers), this module adds the latest chat markers as the last
20 %%% offline messages:
21 %%%
22 %%% * It extracts chat markers data stored for the user in the DB
23 %%% (with timestamps)
24 %%%
25 %%% * Requests cached chat markers from mod_smart_markers that has
26 %%% timestamp older or equal to the stored one.
27 %%%
28 %%% * Generates and inserts chat markers as the last offline messages
29 %%%
30 %%% @end
31 %%%----------------------------------------------------------------------------
32 -module(mod_offline_chatmarkers).
33 -xep([{xep, 160}, {version, "1.0"}]).
34 -behaviour(gen_mod).
35 -behaviour(mongoose_module_metrics).
36
37 %% gen_mod handlers
38 %% gen_mod API
39 -export([start/2]).
40 -export([stop/1]).
41 -export([deps/2]).
42 -export([supported_features/0]).
43
44 %% Hook handlers
45 -export([inspect_packet/4,
46 remove_user/3,
47 pop_offline_messages/2]).
48
49 -ignore_xref([
50 behaviour_info/1, inspect_packet/4, pop_offline_messages/2, remove_user/3
51 ]).
52
53 -include("jlib.hrl").
54 -include_lib("exml/include/exml.hrl").
55
56 %% gen_mod callbacks
57 %% ------------------------------------------------------------------
58
59 -spec supported_features() -> [atom()].
60 supported_features() ->
61
:-(
[dynamic_domains].
62
63 -spec deps(mongooseim:host_type(), gen_mod:module_opts()) -> gen_mod_deps:deps().
64 deps(_,_)->
65
:-(
[]. %% TODO: this need to be marked as required-to-be-configured
66 % [{mod_smart_markers, [], hard}].
67
68 start(HostType, Opts) ->
69
:-(
mod_offline_chatmarkers_backend:init(HostType, add_default_backend(Opts)),
70
:-(
ejabberd_hooks:add(hooks(HostType)),
71
:-(
ok.
72
73 stop(HostType) ->
74
:-(
ejabberd_hooks:delete(hooks(HostType)),
75
:-(
ok.
76
77 hooks(HostType) ->
78
:-(
DefaultHooks = [
79 {offline_message_hook, HostType, ?MODULE, inspect_packet, 40},
80 {resend_offline_messages_hook, HostType, ?MODULE, pop_offline_messages, 60},
81 {remove_user, HostType, ?MODULE, remove_user, 50}
82 ],
83
:-(
case gen_mod:get_module_opt(HostType, ?MODULE, store_groupchat_messages, false) of
84 true ->
85
:-(
GroupChatHook = {offline_groupchat_message_hook,
86 HostType, ?MODULE, inspect_packet, 40},
87
:-(
[GroupChatHook | DefaultHooks];
88
:-(
_ -> DefaultHooks
89 end.
90
91 remove_user(Acc, User, Server) ->
92
:-(
HostType = mongoose_acc:host_type(Acc),
93
:-(
mod_offline_chatmarkers_backend:remove_user(HostType, jid:make(User, Server, <<"">>)),
94
:-(
Acc.
95
96 pop_offline_messages(Acc, JID) ->
97
:-(
mongoose_acc:append(offline, messages, offline_chatmarkers(Acc, JID), Acc).
98
99 inspect_packet(Acc, From, To, Packet) ->
100
:-(
case maybe_store_chat_marker(Acc, From, To, Packet) of
101 true ->
102
:-(
{stop, mongoose_acc:set(offline, stored, true, Acc)};
103 false ->
104
:-(
Acc
105 end.
106
107 maybe_store_chat_marker(Acc, From, To, Packet) ->
108
:-(
HostType = mongoose_acc:host_type(Acc),
109
:-(
case mongoose_acc:get(mod_smart_markers, timestamp, undefined, Acc) of
110
:-(
undefined -> false;
111 Timestamp when is_integer(Timestamp) ->
112
:-(
Room = get_room(Acc, From),
113
:-(
Thread = get_thread(Packet),
114
:-(
mod_offline_chatmarkers_backend:maybe_store(HostType, To, Thread, Room, Timestamp),
115
:-(
true
116 end.
117
118 get_room(Acc, From) ->
119
:-(
case mongoose_acc:stanza_type(Acc) of
120
:-(
<<"groupchat">> -> From;
121
:-(
_ -> undefined
122 end.
123
124 get_thread(El) ->
125
:-(
case exml_query:path(El, [{element, <<"thread">>}, cdata]) of
126
:-(
Thread when Thread =/= <<>> -> Thread;
127
:-(
_ -> undefined
128 end.
129
130 offline_chatmarkers(Acc, JID) ->
131
:-(
HostType = mongoose_acc:host_type(Acc),
132
:-(
{ok, Rows} = mod_offline_chatmarkers_backend:get(HostType, JID),
133
:-(
mod_offline_chatmarkers_backend:remove_user(HostType, JID),
134
:-(
lists:concat([process_row(Acc, JID, R) || R <- Rows]).
135
136 process_row(Acc, Jid, {Thread, undefined, TS}) ->
137
:-(
ChatMarkers = mod_smart_markers:get_chat_markers(Jid, Thread, TS),
138
:-(
[build_one2one_chatmarker_msg(Acc, CM) || CM <- ChatMarkers];
139 process_row(Acc, Jid, {Thread, Room, TS}) ->
140
:-(
ChatMarkers = mod_smart_markers:get_chat_markers(Room, Thread, TS),
141
:-(
[build_room_chatmarker_msg(Acc, Jid, CM) || CM <- ChatMarkers].
142
143 build_one2one_chatmarker_msg(Acc, CM) ->
144
:-(
#{from := From, to := To, thread := Thread,
145 type := Type, id := Id, timestamp := TS} = CM,
146
:-(
Children = thread(Thread) ++ marker(Type, Id),
147
:-(
Attributes = [{<<"from">>, jid:to_binary(From)},
148 {<<"to">>, jid:to_binary(To)}],
149
:-(
Packet = #xmlel{name = <<"message">>, attrs = Attributes, children = Children},
150
:-(
make_route_item(Acc, From, To, TS, Packet).
151
152 build_room_chatmarker_msg(Acc, To, CM) ->
153
:-(
#{from := FromUser, to := Room, thread := Thread,
154 type := Type, id := Id, timestamp := TS} = CM,
155
:-(
FromUserBin = jid:to_binary(jid:to_lus(FromUser)),
156
:-(
From = jid:make(Room#jid.luser, Room#jid.lserver, FromUserBin),
157
:-(
FromBin = jid:to_binary(From),
158
:-(
Children = thread(Thread) ++ marker(Type, Id),
159
:-(
Attributes = [{<<"from">>, FromBin},
160 {<<"to">>, jid:to_binary(To)},
161 {<<"type">>, <<"groupchat">>}],
162
:-(
Packet = #xmlel{name = <<"message">>, attrs = Attributes, children = Children},
163
:-(
make_route_item(Acc, From, To, TS, Packet).
164
165 make_route_item(Acc, From, To, TS, Packet) ->
166
:-(
NewStanzaParams = #{element => Packet, from_jid => From, to_jid => To},
167
:-(
Acc1 = mongoose_acc:update_stanza(NewStanzaParams, Acc),
168
:-(
Acc2 = mongoose_acc:set_permanent(mod_smart_markers, timestamp, TS, Acc1),
169
:-(
{route, From, To, Acc2}.
170
171 marker(Type, Id) ->
172
:-(
[#xmlel{name = atom_to_binary(Type, latin1),
173 attrs = [{<<"xmlns">>, <<"urn:xmpp:chat-markers:0">>},
174 {<<"id">>, Id}], children = []}].
175
176
:-(
thread(undefined) -> [];
177 thread(Thread) ->
178
:-(
[#xmlel{name = <<"thread">>, attrs = [],
179 children = [#xmlcdata{content = Thread}]}].
180
181 add_default_backend(Opts) ->
182
:-(
case lists:keyfind(backend, 2, Opts) of
183 false ->
184
:-(
[{backend, rdbms} | Opts];
185 _ ->
186
:-(
Opts
187 end.
Line Hits Source