./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 1 [dynamic_domains].
62
63 -spec deps(mongooseim:host_type(), gen_mod:module_opts()) -> gen_mod:deps_list().
64 deps(_,_)->
65 5 [{mod_smart_markers, hard}].
66
67 start(HostType, Opts) ->
68 1 mod_offline_chatmarkers_backend:init(HostType, add_default_backend(Opts)),
69 1 ejabberd_hooks:add(hooks(HostType)),
70 1 ok.
71
72 stop(HostType) ->
73 1 ejabberd_hooks:delete(hooks(HostType)),
74 1 ok.
75
76 hooks(HostType) ->
77 2 DefaultHooks = [
78 {offline_message_hook, HostType, ?MODULE, inspect_packet, 40},
79 {resend_offline_messages_hook, HostType, ?MODULE, pop_offline_messages, 60},
80 {remove_user, HostType, ?MODULE, remove_user, 50}
81 ],
82 2 case gen_mod:get_module_opt(HostType, ?MODULE, store_groupchat_messages, false) of
83 true ->
84 2 GroupChatHook = {offline_groupchat_message_hook,
85 HostType, ?MODULE, inspect_packet, 40},
86 2 [GroupChatHook | DefaultHooks];
87
:-(
_ -> DefaultHooks
88 end.
89
90 remove_user(Acc, User, Server) ->
91
:-(
HostType = mongoose_acc:host_type(Acc),
92
:-(
mod_offline_chatmarkers_backend:remove_user(HostType, jid:make(User, Server, <<"">>)),
93
:-(
Acc.
94
95 pop_offline_messages(Acc, JID) ->
96 6 mongoose_acc:append(offline, messages, offline_chatmarkers(Acc, JID), Acc).
97
98 inspect_packet(Acc, From, To, Packet) ->
99 12 case maybe_store_chat_marker(Acc, From, To, Packet) of
100 true ->
101 10 {stop, mongoose_acc:set(offline, stored, true, Acc)};
102 false ->
103 2 Acc
104 end.
105
106 maybe_store_chat_marker(Acc, From, To, Packet) ->
107 12 HostType = mongoose_acc:host_type(Acc),
108 12 case mongoose_acc:get(mod_smart_markers, timestamp, undefined, Acc) of
109 2 undefined -> false;
110 Timestamp when is_integer(Timestamp) ->
111 10 Room = get_room(Acc, From),
112 10 Thread = get_thread(Packet),
113 10 mod_offline_chatmarkers_backend:maybe_store(HostType, To, Thread, Room, Timestamp),
114 10 true
115 end.
116
117 get_room(Acc, From) ->
118 10 case mongoose_acc:stanza_type(Acc) of
119 5 <<"groupchat">> -> From;
120 5 _ -> undefined
121 end.
122
123 get_thread(El) ->
124 10 case exml_query:path(El, [{element, <<"thread">>}, cdata]) of
125 10 Thread when Thread =/= <<>> -> Thread;
126
:-(
_ -> undefined
127 end.
128
129 offline_chatmarkers(Acc, JID) ->
130 6 HostType = mongoose_acc:host_type(Acc),
131 6 {ok, Rows} = mod_offline_chatmarkers_backend:get(HostType, JID),
132 6 mod_offline_chatmarkers_backend:remove_user(HostType, JID),
133 6 lists:concat([process_row(Acc, JID, R) || R <- Rows]).
134
135 process_row(Acc, Jid, {Thread, undefined, TS}) ->
136 2 ChatMarkers = mod_smart_markers:get_chat_markers(Jid, Thread, TS),
137 2 [build_one2one_chatmarker_msg(Acc, CM) || CM <- ChatMarkers];
138 process_row(Acc, Jid, {Thread, Room, TS}) ->
139 2 ChatMarkers = mod_smart_markers:get_chat_markers(Room, Thread, TS),
140 2 [build_room_chatmarker_msg(Acc, Jid, CM) || CM <- ChatMarkers].
141
142 build_one2one_chatmarker_msg(Acc, CM) ->
143 3 #{from := From, to := To, thread := Thread,
144 type := Type, id := Id, timestamp := TS} = CM,
145 3 Children = thread(Thread) ++ marker(Type, Id),
146 3 Attributes = [{<<"from">>, jid:to_binary(From)},
147 {<<"to">>, jid:to_binary(To)}],
148 3 Packet = #xmlel{name = <<"message">>, attrs = Attributes, children = Children},
149 3 make_route_item(Acc, From, To, TS, Packet).
150
151 build_room_chatmarker_msg(Acc, To, CM) ->
152 3 #{from := FromUser, to := Room, thread := Thread,
153 type := Type, id := Id, timestamp := TS} = CM,
154 3 FromUserBin = jid:to_binary(jid:to_lus(FromUser)),
155 3 From = jid:make(Room#jid.luser, Room#jid.lserver, FromUserBin),
156 3 FromBin = jid:to_binary(From),
157 3 Children = thread(Thread) ++ marker(Type, Id),
158 3 Attributes = [{<<"from">>, FromBin},
159 {<<"to">>, jid:to_binary(To)},
160 {<<"type">>, <<"groupchat">>}],
161 3 Packet = #xmlel{name = <<"message">>, attrs = Attributes, children = Children},
162 3 make_route_item(Acc, From, To, TS, Packet).
163
164 make_route_item(Acc, From, To, TS, Packet) ->
165 6 NewStanzaParams = #{element => Packet, from_jid => From, to_jid => To},
166 6 Acc1 = mongoose_acc:update_stanza(NewStanzaParams, Acc),
167 6 Acc2 = mongoose_acc:set_permanent(mod_smart_markers, timestamp, TS, Acc1),
168 6 {route, From, To, Acc2}.
169
170 marker(Type, Id) ->
171 6 [#xmlel{name = atom_to_binary(Type, latin1),
172 attrs = [{<<"xmlns">>, <<"urn:xmpp:chat-markers:0">>},
173 {<<"id">>, Id}], children = []}].
174
175 4 thread(undefined) -> [];
176 thread(Thread) ->
177 2 [#xmlel{name = <<"thread">>, attrs = [],
178 children = [#xmlcdata{content = Thread}]}].
179
180 add_default_backend(Opts) ->
181 1 case lists:keyfind(backend, 2, Opts) of
182 false ->
183 1 [{backend, rdbms} | Opts];
184 _ ->
185
:-(
Opts
186 end.
Line Hits Source