./ct_report/coverage/mod_mam_rdbms_prefs.COVER.html

1 %%%-------------------------------------------------------------------
2 %%% @author Uvarov Michael <arcusfelis@gmail.com>
3 %%% @copyright (C) 2013, Uvarov Michael
4 %%% @doc A backend for storing MAM preferencies using RDBMS.
5 %%% @end
6 %%%-------------------------------------------------------------------
7 -module(mod_mam_rdbms_prefs).
8
9 %% ----------------------------------------------------------------------
10 %% Exports
11
12 %% gen_mod handlers
13 -export([start/2, stop/1, supported_features/0]).
14
15 %% MAM hook handlers
16 -behaviour(ejabberd_gen_mam_prefs).
17 -export([get_behaviour/5,
18 get_prefs/4,
19 set_prefs/7,
20 remove_archive/4]).
21
22 -ignore_xref([remove_archive/4, start/2, stop/1, supported_features/0]).
23
24 -import(mongoose_rdbms, [prepare/4]).
25
26 -include("mongoose.hrl").
27 -include("jlib.hrl").
28 -include_lib("exml/include/exml.hrl").
29
30
31 %% ----------------------------------------------------------------------
32 %% gen_mod callbacks
33 %% Starting and stopping functions for users' archives
34 -spec start(mongooseim:host_type(), _) -> ok.
35 start(HostType, _Opts) ->
36 50 prepare_queries(HostType),
37 50 ejabberd_hooks:add(hooks(HostType)),
38 50 ok.
39
40 -spec stop(mongooseim:host_type()) -> ok.
41 stop(HostType) ->
42 50 ejabberd_hooks:delete(hooks(HostType)),
43 50 ok.
44
45 -spec supported_features() -> [atom()].
46 supported_features() ->
47 50 [dynamic_domains].
48
49 hooks(HostType) ->
50 100 PM = gen_mod:get_module_opt(HostType, ?MODULE, pm, false),
51 100 MUC = gen_mod:get_module_opt(HostType, ?MODULE, muc, false),
52 100 maybe_pm_hooks(PM, HostType) ++ maybe_muc_hooks(MUC, HostType).
53
54 78 maybe_pm_hooks(true, HostType) -> pm_hooks(HostType);
55 22 maybe_pm_hooks(false, _HostType) -> [].
56
57 28 maybe_muc_hooks(true, HostType) -> muc_hooks(HostType);
58 72 maybe_muc_hooks(false, _HostType) -> [].
59
60 pm_hooks(HostType) ->
61 78 [{mam_get_behaviour, HostType, ?MODULE, get_behaviour, 50},
62 {mam_get_prefs, HostType, ?MODULE, get_prefs, 50},
63 {mam_set_prefs, HostType, ?MODULE, set_prefs, 50},
64 {mam_remove_archive, HostType, ?MODULE, remove_archive, 50}].
65
66 muc_hooks(HostType) ->
67 28 [{mam_muc_get_behaviour, HostType, ?MODULE, get_behaviour, 50},
68 {mam_muc_get_prefs, HostType, ?MODULE, get_prefs, 50},
69 {mam_muc_set_prefs, HostType, ?MODULE, set_prefs, 50},
70 {mam_muc_remove_archive, HostType, ?MODULE, remove_archive, 50}].
71
72 %% Prepared queries
73 prepare_queries(HostType) ->
74 50 prepare(mam_prefs_insert, mam_config, [user_id, remote_jid, behaviour],
75 <<"INSERT INTO mam_config(user_id, remote_jid, behaviour) "
76 "VALUES (?, ?, ?)">>),
77 50 prepare(mam_prefs_select, mam_config, [user_id],
78 <<"SELECT remote_jid, behaviour "
79 "FROM mam_config WHERE user_id=?">>),
80 50 prepare(mam_prefs_select_behaviour, mam_config,
81 [user_id, remote_jid],
82 <<"SELECT remote_jid, behaviour "
83 "FROM mam_config "
84 "WHERE user_id=? "
85 "AND (remote_jid='' OR remote_jid=?)">>),
86 50 prepare(mam_prefs_select_behaviour2, mam_config,
87 [user_id, remote_jid, remote_jid],
88 <<"SELECT remote_jid, behaviour "
89 "FROM mam_config "
90 "WHERE user_id=? "
91 "AND (remote_jid='' OR remote_jid=? OR remote_jid=?)">>),
92 50 OrdBy = order_by_remote_jid_in_delete(HostType),
93 50 prepare(mam_prefs_delete, mam_config, [user_id],
94 <<"DELETE FROM mam_config WHERE user_id=?", OrdBy/binary>>),
95 50 ok.
96
97 order_by_remote_jid_in_delete(HostType) ->
98 50 case mongoose_rdbms:db_engine(HostType) of
99
:-(
mysql -> <<" ORDER BY remote_jid">>;
100 50 _ -> <<>>
101 end.
102
103 %% ----------------------------------------------------------------------
104 %% Internal functions and callbacks
105
106 -spec get_behaviour(Default :: mod_mam:archive_behaviour(),
107 HostType :: mongooseim:host_type(), ArchiveID :: mod_mam:archive_id(),
108 LocJID :: jid:jid(), RemJID :: jid:jid()) -> any().
109 get_behaviour(DefaultBehaviour, HostType, UserID, _LocJID, RemJID)
110 when is_integer(UserID) ->
111 2109 RemLJID = jid:to_lower(RemJID),
112 2109 BRemLBareJID = jid:to_binary(jid:to_bare(RemLJID)),
113 2109 BRemLJID = jid:to_binary(RemLJID),
114 2109 case query_behaviour(HostType, UserID, BRemLJID, BRemLBareJID) of
115 {selected, []} ->
116 1689 DefaultBehaviour;
117 {selected, RemoteJid2Behaviour} ->
118 420 DbBehaviour = choose_behaviour(BRemLJID, BRemLBareJID, RemoteJid2Behaviour),
119 420 decode_behaviour(DbBehaviour)
120 end.
121
122 -spec choose_behaviour(binary(), binary(), [{binary(), binary()}]) -> binary().
123 choose_behaviour(BRemLJID, BRemLBareJID, RemoteJid2Behaviour) ->
124 420 case lists:keyfind(BRemLJID, 1, RemoteJid2Behaviour) of
125 {_, Behaviour} ->
126
:-(
Behaviour;
127 false ->
128 420 case lists:keyfind(BRemLBareJID, 1, RemoteJid2Behaviour) of
129 {_, Behaviour} ->
130 240 Behaviour;
131 false ->
132 %% Only one key remains
133 180 {_, Behaviour} = lists:keyfind(<<>>, 1, RemoteJid2Behaviour),
134 180 Behaviour
135 end
136 end.
137
138 -spec set_prefs(Result :: any(), HostType :: mongooseim:host_type(),
139 ArchiveID :: mod_mam:archive_id(), ArchiveJID :: jid:jid(),
140 DefaultMode :: mod_mam:archive_behaviour(),
141 AlwaysJIDs :: [jid:literal_jid()],
142 NeverJIDs :: [jid:literal_jid()]) -> any().
143 set_prefs(_Result, HostType, UserID, _ArcJID, DefaultMode, AlwaysJIDs, NeverJIDs) ->
144 220 try
145 220 set_prefs1(HostType, UserID, DefaultMode, AlwaysJIDs, NeverJIDs)
146 catch _Type:Error ->
147
:-(
{error, Error}
148 end.
149
150 set_prefs1(HostType, UserID, DefaultMode, AlwaysJIDs, NeverJIDs) ->
151 220 Rows = prefs_to_rows(UserID, DefaultMode, AlwaysJIDs, NeverJIDs),
152 %% MySQL sometimes aborts transaction with reason:
153 %% "Deadlock found when trying to get lock; try restarting transaction"
154 220 mongoose_rdbms:transaction_with_delayed_retry(HostType, fun() ->
155 220 {updated, _} =
156 mongoose_rdbms:execute(HostType, mam_prefs_delete, [UserID]),
157 220 [{updated, 1} = mongoose_rdbms:execute(HostType, mam_prefs_insert, Row) || Row <- Rows],
158 220 ok
159 end, #{user_id => UserID, retries => 5, delay => 100}),
160 220 ok.
161
162 -spec get_prefs(mod_mam:preference(), HostType :: mongooseim:host_type(),
163 ArchiveID :: mod_mam:archive_id(), ArchiveJID :: jid:jid())
164 -> mod_mam:preference().
165 get_prefs({GlobalDefaultMode, _, _}, HostType, UserID, _ArcJID) ->
166 115 {selected, Rows} = mongoose_rdbms:execute(HostType, mam_prefs_select, [UserID]),
167 115 decode_prefs_rows(Rows, GlobalDefaultMode, [], []).
168
169 -spec remove_archive(mongoose_acc:t(), mongooseim:host_type(),
170 mod_mam:archive_id(), jid:jid()) ->
171 mongoose_acc:t().
172 remove_archive(Acc, HostType, UserID, _ArcJID) ->
173 556 remove_archive(HostType, UserID),
174 556 Acc.
175
176 remove_archive(HostType, UserID) ->
177 556 {updated, _} =
178 mongoose_rdbms:execute(HostType, mam_prefs_delete, [UserID]).
179
180 -spec query_behaviour(HostType :: mongooseim:host_type(),
181 UserID :: non_neg_integer(),
182 BRemLJID :: binary(),
183 BRemLBareJID :: binary()
184 ) -> any().
185 query_behaviour(HostType, UserID, BRemLJID, BRemLJID) ->
186 204 mongoose_rdbms:execute(HostType, mam_prefs_select_behaviour,
187 [UserID, BRemLJID]); %% check just bare jid
188 query_behaviour(HostType, UserID, BRemLJID, BRemLBareJID) ->
189 1905 mongoose_rdbms:execute(HostType, mam_prefs_select_behaviour2,
190 [UserID, BRemLJID, BRemLBareJID]).
191
192 %% ----------------------------------------------------------------------
193 %% Helpers
194
195 -spec encode_behaviour(always | never | roster) -> binary().
196 80 encode_behaviour(roster) -> <<"R">>;
197 205 encode_behaviour(always) -> <<"A">>;
198 195 encode_behaviour(never) -> <<"N">>.
199
200 -spec decode_behaviour(binary()) -> always | never | roster.
201 105 decode_behaviour(<<"R">>) -> roster;
202 215 decode_behaviour(<<"A">>) -> always;
203 215 decode_behaviour(<<"N">>) -> never.
204
205 prefs_to_rows(UserID, DefaultMode, AlwaysJIDs, NeverJIDs) ->
206 220 AlwaysRows = [[UserID, JID, encode_behaviour(always)] || JID <- AlwaysJIDs],
207 220 NeverRows = [[UserID, JID, encode_behaviour(never)] || JID <- NeverJIDs],
208 220 DefaultRow = [UserID, <<>>, encode_behaviour(DefaultMode)],
209 %% Lock keys in the same order to avoid deadlock
210 220 [DefaultRow|lists:sort(AlwaysRows ++ NeverRows)].
211
212 -spec decode_prefs_rows([{binary() | jid:jid(), binary()}],
213 DefaultMode :: mod_mam:archive_behaviour(),
214 AlwaysJIDs :: [jid:literal_jid()],
215 NeverJIDs :: [jid:literal_jid()]) ->
216 {mod_mam:archive_behaviour(), [jid:literal_jid()], [jid:literal_jid()]}.
217 decode_prefs_rows([{<<>>, Behaviour}|Rows], _DefaultMode, AlwaysJIDs, NeverJIDs) ->
218 115 decode_prefs_rows(Rows, decode_behaviour(Behaviour), AlwaysJIDs, NeverJIDs);
219 decode_prefs_rows([{JID, <<"A">>}|Rows], DefaultMode, AlwaysJIDs, NeverJIDs) ->
220 75 decode_prefs_rows(Rows, DefaultMode, [JID|AlwaysJIDs], NeverJIDs);
221 decode_prefs_rows([{JID, <<"N">>}|Rows], DefaultMode, AlwaysJIDs, NeverJIDs) ->
222 65 decode_prefs_rows(Rows, DefaultMode, AlwaysJIDs, [JID|NeverJIDs]);
223 decode_prefs_rows([], DefaultMode, AlwaysJIDs, NeverJIDs) ->
224 115 {DefaultMode, AlwaysJIDs, NeverJIDs}.
Line Hits Source