./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
:-(
prepare_queries(HostType),
36
:-(
ok.
37
38 -spec stop(mongooseim:host_type()) -> ok.
39 stop(_HostType) ->
40
:-(
ok.
41
42 -spec supported_features() -> [atom()].
43 supported_features() ->
44
:-(
[dynamic_domains].
45
46 hooks(HostType) ->
47
:-(
PM = gen_mod:get_module_opt(HostType, ?MODULE, pm, false),
48
:-(
MUC = gen_mod:get_module_opt(HostType, ?MODULE, muc, false),
49
:-(
maybe_pm_hooks(PM, HostType) ++ maybe_muc_hooks(MUC, HostType).
50
51
:-(
maybe_pm_hooks(true, HostType) -> pm_hooks(HostType);
52
:-(
maybe_pm_hooks(false, _HostType) -> [].
53
54
:-(
maybe_muc_hooks(true, HostType) -> muc_hooks(HostType);
55
:-(
maybe_muc_hooks(false, _HostType) -> [].
56
57 pm_hooks(HostType) ->
58
:-(
[{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
:-(
[{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
:-(
prepare(mam_prefs_insert, mam_config, [user_id, remote_jid, behaviour],
72 <<"INSERT INTO mam_config(user_id, remote_jid, behaviour) "
73 "VALUES (?, ?, ?)">>),
74
:-(
prepare(mam_prefs_select, mam_config, [user_id],
75 <<"SELECT remote_jid, behaviour "
76 "FROM mam_config WHERE user_id=?">>),
77
:-(
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
:-(
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
:-(
OrdBy = order_by_remote_jid_in_delete(HostType),
90
:-(
prepare(mam_prefs_delete, mam_config, [user_id],
91 <<"DELETE FROM mam_config WHERE user_id=?", OrdBy/binary>>),
92
:-(
ok.
93
94 order_by_remote_jid_in_delete(HostType) ->
95
:-(
case mongoose_rdbms:db_engine(HostType) of
96
:-(
mysql -> <<" ORDER BY remote_jid">>;
97
:-(
_ -> <<>>
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
:-(
RemLJID = jid:to_lower(RemJID),
112
:-(
BRemLBareJID = jid:to_bare_binary(RemLJID),
113
:-(
BRemLJID = jid:to_binary(RemLJID),
114
:-(
case query_behaviour(HostType, UserID, BRemLJID, BRemLBareJID) of
115 {selected, []} ->
116
:-(
{ok, DefaultBehaviour};
117 {selected, RemoteJid2Behaviour} ->
118
:-(
DbBehaviour = choose_behaviour(BRemLJID, BRemLBareJID, RemoteJid2Behaviour),
119
:-(
{ok, decode_behaviour(DbBehaviour)}
120 end.
121
122 -spec choose_behaviour(binary(), binary(), [{binary(), binary()}]) -> binary().
123 choose_behaviour(BRemLJID, BRemLBareJID, RemoteJid2Behaviour) ->
124
:-(
case lists:keyfind(BRemLJID, 1, RemoteJid2Behaviour) of
125 {_, Behaviour} ->
126
:-(
Behaviour;
127 false ->
128
:-(
case lists:keyfind(BRemLBareJID, 1, RemoteJid2Behaviour) of
129 {_, Behaviour} ->
130
:-(
Behaviour;
131 false ->
132 %% Only one key remains
133
:-(
{_, Behaviour} = lists:keyfind(<<>>, 1, RemoteJid2Behaviour),
134
:-(
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
:-(
try
147
:-(
{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
:-(
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
:-(
mongoose_rdbms:transaction_with_delayed_retry(HostType, fun() ->
157
:-(
{updated, _} =
158 mongoose_rdbms:execute(HostType, mam_prefs_delete, [UserID]),
159
:-(
[{updated, 1} = mongoose_rdbms:execute(HostType, mam_prefs_insert, Row) || Row <- Rows],
160
:-(
ok
161 end, #{user_id => UserID, retries => 5, delay => 100}),
162
:-(
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
:-(
{selected, Rows} = mongoose_rdbms:execute(HostType, mam_prefs_select, [UserID]),
170
:-(
{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
:-(
remove_archive(HostType, UserID),
178
:-(
{ok, Acc}.
179
180 remove_archive(HostType, UserID) ->
181
:-(
{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
:-(
mongoose_rdbms:execute(HostType, mam_prefs_select_behaviour,
191 [UserID, BRemLJID]); %% check just bare jid
192 query_behaviour(HostType, UserID, BRemLJID, BRemLBareJID) ->
193
:-(
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
:-(
encode_behaviour(roster) -> <<"R">>;
201
:-(
encode_behaviour(always) -> <<"A">>;
202
:-(
encode_behaviour(never) -> <<"N">>.
203
204 -spec decode_behaviour(binary()) -> always | never | roster.
205
:-(
decode_behaviour(<<"R">>) -> roster;
206
:-(
decode_behaviour(<<"A">>) -> always;
207
:-(
decode_behaviour(<<"N">>) -> never.
208
209 prefs_to_rows(UserID, DefaultMode, AlwaysJIDs, NeverJIDs) ->
210
:-(
AlwaysRows = [[UserID, JID, encode_behaviour(always)] || JID <- AlwaysJIDs],
211
:-(
NeverRows = [[UserID, JID, encode_behaviour(never)] || JID <- NeverJIDs],
212
:-(
DefaultRow = [UserID, <<>>, encode_behaviour(DefaultMode)],
213 %% Lock keys in the same order to avoid deadlock
214
:-(
[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
:-(
decode_prefs_rows(Rows, decode_behaviour(Behaviour), AlwaysJIDs, NeverJIDs);
223 decode_prefs_rows([{JID, <<"A">>}|Rows], DefaultMode, AlwaysJIDs, NeverJIDs) ->
224
:-(
decode_prefs_rows(Rows, DefaultMode, [JID|AlwaysJIDs], NeverJIDs);
225 decode_prefs_rows([{JID, <<"N">>}|Rows], DefaultMode, AlwaysJIDs, NeverJIDs) ->
226
:-(
decode_prefs_rows(Rows, DefaultMode, AlwaysJIDs, [JID|NeverJIDs]);
227 decode_prefs_rows([], DefaultMode, AlwaysJIDs, NeverJIDs) ->
228
:-(
{DefaultMode, AlwaysJIDs, NeverJIDs}.
Line Hits Source