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 |
45 |
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 |
1992 |
RemLJID = jid:to_lower(RemJID), |
112 |
1992 |
BRemLBareJID = jid:to_bare_binary(RemLJID), |
113 |
1992 |
BRemLJID = jid:to_binary(RemLJID), |
114 |
1992 |
case query_behaviour(HostType, UserID, BRemLJID, BRemLBareJID) of |
115 |
|
{selected, []} -> |
116 |
1572 |
{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 |
514 |
remove_archive(HostType, UserID), |
178 |
514 |
{ok, Acc}. |
179 |
|
|
180 |
|
remove_archive(HostType, UserID) -> |
181 |
514 |
{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 |
95 |
mongoose_rdbms:execute(HostType, mam_prefs_select_behaviour, |
191 |
|
[UserID, BRemLJID]); %% check just bare jid |
192 |
|
query_behaviour(HostType, UserID, BRemLJID, BRemLBareJID) -> |
193 |
1897 |
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}. |