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