1 |
|
%%%------------------------------------------------------------------- |
2 |
|
%%% @author Uvarov Michael <arcusfelis@gmail.com> |
3 |
|
%%% @copyright (C) 2013, Uvarov Michael |
4 |
|
%%% @doc A backend for storing MAM preferencies using Cassandra. |
5 |
|
%%% @end |
6 |
|
%%%------------------------------------------------------------------- |
7 |
|
-module(mod_mam_cassandra_prefs). |
8 |
|
-behaviour(mongoose_cassandra). |
9 |
|
-behaviour(gen_mod). |
10 |
|
|
11 |
|
%% ---------------------------------------------------------------------- |
12 |
|
%% Exports |
13 |
|
|
14 |
|
%% gen_mod handlers |
15 |
|
-export([start/2, stop/1]). |
16 |
|
|
17 |
|
%% MAM hook handlers |
18 |
|
-behaviour(ejabberd_gen_mam_prefs). |
19 |
|
-export([get_behaviour/3, |
20 |
|
get_prefs/3, |
21 |
|
set_prefs/3, |
22 |
|
remove_archive/3]). |
23 |
|
|
24 |
|
-export([prepared_queries/0]). |
25 |
|
|
26 |
|
-include("mongoose.hrl"). |
27 |
|
-include("jlib.hrl"). |
28 |
|
-include_lib("exml/include/exml.hrl"). |
29 |
|
|
30 |
|
-type host_type() :: mongooseim:host_type(). |
31 |
|
|
32 |
|
%% ---------------------------------------------------------------------- |
33 |
|
%% gen_mod callbacks |
34 |
|
%% Starting and stopping functions for users' archives |
35 |
|
|
36 |
|
-spec start(host_type(), gen_mod:module_opts()) -> ok. |
37 |
|
start(HostType, Opts) -> |
38 |
9 |
gen_hook:add_handlers(hooks(HostType, Opts)). |
39 |
|
|
40 |
|
-spec stop(host_type()) -> ok. |
41 |
|
stop(HostType) -> |
42 |
9 |
Opts = gen_mod:get_loaded_module_opts(HostType, ?MODULE), |
43 |
9 |
gen_hook:delete_handlers(hooks(HostType, Opts)). |
44 |
|
|
45 |
|
%% ---------------------------------------------------------------------- |
46 |
|
%% Hooks |
47 |
|
|
48 |
|
hooks(HostType, Opts) -> |
49 |
18 |
lists:flatmap(fun(Type) -> hooks(HostType, Type, Opts) end, [pm, muc]). |
50 |
|
|
51 |
|
hooks(HostType, pm, #{pm := true}) -> |
52 |
16 |
[{mam_get_behaviour, HostType, fun ?MODULE:get_behaviour/3, #{}, 50}, |
53 |
|
{mam_get_prefs, HostType, fun ?MODULE:get_prefs/3, #{}, 50}, |
54 |
|
{mam_set_prefs, HostType, fun ?MODULE:set_prefs/3, #{}, 50}, |
55 |
|
{mam_remove_archive, HostType, fun ?MODULE:remove_archive/3, #{}, 50}]; |
56 |
|
hooks(HostType, muc, #{muc := true}) -> |
57 |
4 |
[{mam_muc_get_behaviour, HostType, fun ?MODULE:get_behaviour/3, #{}, 50}, |
58 |
|
{mam_muc_get_prefs, HostType, fun ?MODULE:get_prefs/3, #{}, 50}, |
59 |
|
{mam_muc_set_prefs, HostType, fun ?MODULE:set_prefs/3, #{}, 50}, |
60 |
|
{mam_muc_remove_archive, HostType, fun ?MODULE:remove_archive/3, #{}, 50}]; |
61 |
|
hooks(_HostType, _Opt, _Opts) -> |
62 |
16 |
[]. |
63 |
|
|
64 |
|
%% ---------------------------------------------------------------------- |
65 |
|
|
66 |
|
prepared_queries() -> |
67 |
536 |
[ |
68 |
|
{set_prefs_ts_query, |
69 |
|
"INSERT INTO mam_config(user_jid, remote_jid, behaviour) VALUES (?, ?, ?) USING TIMESTAMP ?"}, |
70 |
|
{get_prefs_query, |
71 |
|
"SELECT remote_jid, behaviour FROM mam_config WHERE user_jid = ?"}, |
72 |
|
{get_behaviour_bare_query, |
73 |
|
"SELECT remote_jid, behaviour FROM mam_config WHERE user_jid = ? AND remote_jid IN ('', ?)"}, |
74 |
|
{get_behaviour_full_query, |
75 |
|
"SELECT remote_jid, behaviour FROM mam_config WHERE user_jid = ? AND remote_jid " |
76 |
|
"IN ('', :start_remote_jid, :end_remote_jid)"}, |
77 |
|
{del_prefs_ts_query, |
78 |
|
"DELETE FROM mam_config USING TIMESTAMP ? WHERE user_jid = ?"} |
79 |
|
]. |
80 |
|
|
81 |
|
%% ---------------------------------------------------------------------- |
82 |
|
%% Internal functions and callbacks |
83 |
|
|
84 |
|
-spec get_behaviour(Acc, Params, Extra) -> {ok, Acc} when |
85 |
|
Acc :: mod_mam:archive_behaviour(), |
86 |
|
Params :: ejabberd_gen_mam_prefs:get_behaviour_params(), |
87 |
|
Extra :: gen_hook:extra(). |
88 |
|
get_behaviour(DefaultBehaviour, |
89 |
|
#{owner := LocJID, remote := RemJID}, |
90 |
|
#{host_type := HostType}) -> |
91 |
278 |
get_behaviour2(DefaultBehaviour, LocJID, RemJID, HostType); |
92 |
|
get_behaviour(DefaultBehaviour, |
93 |
|
#{room := LocJID, remote := RemJID}, |
94 |
|
#{host_type := HostType}) -> |
95 |
46 |
get_behaviour2(DefaultBehaviour, LocJID, RemJID, HostType). |
96 |
|
|
97 |
|
get_behaviour2(DefaultBehaviour, LocJID, RemJID, HostType) -> |
98 |
324 |
BUserJID = mod_mam_utils:bare_jid(LocJID), |
99 |
324 |
BRemBareJID = mod_mam_utils:bare_jid(RemJID), |
100 |
324 |
BRemJID = mod_mam_utils:full_jid(RemJID), |
101 |
324 |
case query_behaviour(HostType, LocJID, BUserJID, BRemJID, BRemBareJID) of |
102 |
|
{ok, []} -> |
103 |
240 |
{ok, DefaultBehaviour}; |
104 |
|
{ok, [_ | _] = Rows} -> |
105 |
|
%% After sort <<>>, <<"a">>, <<"a/b">> |
106 |
84 |
SortedRows = lists:sort( |
107 |
|
fun(#{remote_jid := JID1, behaviour := B1}, |
108 |
|
#{remote_jid := JID2, behaviour := B2}) -> |
109 |
48 |
{JID1, B1} < {JID2, B2} |
110 |
|
end, Rows), |
111 |
84 |
#{behaviour := Behaviour} = lists:last(SortedRows), |
112 |
84 |
{ok, decode_behaviour(Behaviour)} |
113 |
|
end. |
114 |
|
|
115 |
|
|
116 |
|
-spec set_prefs(Acc, Params, Extra) -> {ok, Acc} when |
117 |
|
Acc :: term(), |
118 |
|
Params :: ejabberd_gen_mam_prefs:set_prefs_params(), |
119 |
|
Extra :: gen_hook:extra(). |
120 |
|
set_prefs(_Result, |
121 |
|
#{owner := UserJID, default_mode := DefaultMode, always_jids := AlwaysJIDs, |
122 |
|
never_jids := NeverJIDs}, |
123 |
|
#{host_type := HostType}) -> |
124 |
44 |
try |
125 |
44 |
{ok, set_prefs1(HostType, UserJID, DefaultMode, AlwaysJIDs, NeverJIDs)} |
126 |
|
catch Type:Error:StackTrace -> |
127 |
:-( |
?LOG_ERROR(#{what => mam_set_prefs_failed, |
128 |
|
user_jid => UserJID, default_mode => DefaultMode, |
129 |
|
always_jids => AlwaysJIDs, never_jids => NeverJIDs, |
130 |
:-( |
class => Type, reason => Error, stacktrace => StackTrace}), |
131 |
:-( |
{ok, {error, Error}} |
132 |
|
end. |
133 |
|
|
134 |
|
set_prefs1(HostType, UserJID, DefaultMode, AlwaysJIDs, NeverJIDs) -> |
135 |
44 |
BUserJID = mod_mam_utils:bare_jid(UserJID), |
136 |
|
%% Force order of operations using timestamps |
137 |
|
%% http://stackoverflow.com/questions/30317877/cassandra-batch-statement-execution-order |
138 |
44 |
Now = mongoose_cassandra:now_timestamp(), |
139 |
44 |
Next = Now + 1, |
140 |
44 |
DelParams = #{'[timestamp]' => Now, user_jid => BUserJID}, |
141 |
44 |
MultiParams = [encode_row(BUserJID, <<>>, encode_behaviour(DefaultMode), Next)] |
142 |
27 |
++ [encode_row(BUserJID, BinJID, <<"A">>, Next) || BinJID <- AlwaysJIDs] |
143 |
25 |
++ [encode_row(BUserJID, BinJID, <<"N">>, Next) || BinJID <- NeverJIDs], |
144 |
44 |
DelQuery = {del_prefs_ts_query, [DelParams]}, |
145 |
44 |
SetQuery = {set_prefs_ts_query, MultiParams}, |
146 |
44 |
Queries = [DelQuery, SetQuery], |
147 |
44 |
Res = [mongoose_cassandra:cql_write(pool_name(HostType), UserJID, ?MODULE, Query, Params) |
148 |
44 |
|| {Query, Params} <- Queries], |
149 |
44 |
?LOG_DEBUG(#{what => mam_set_prefs, user_jid => UserJID, default_mode => DefaultMode, |
150 |
44 |
always_jids => AlwaysJIDs, never_jids => NeverJIDs, result => Res}), |
151 |
44 |
ok. |
152 |
|
|
153 |
|
encode_row(BUserJID, BRemoteJID, Behaviour, Timestamp) -> |
154 |
96 |
#{user_jid => BUserJID, remote_jid => BRemoteJID, |
155 |
|
behaviour => Behaviour, '[timestamp]' => Timestamp}. |
156 |
|
|
157 |
|
|
158 |
|
-spec get_prefs(Acc, Params, Extra) -> {ok, Acc} when |
159 |
|
Acc :: mod_mam:preference(), |
160 |
|
Params :: ejabberd_gen_mam_prefs:get_prefs_params(), |
161 |
|
Extra :: gen_hook:extra(). |
162 |
|
get_prefs({GlobalDefaultMode, _, _}, #{owner := UserJID}, #{host_type := HostType}) -> |
163 |
23 |
BUserJID = mod_mam_utils:bare_jid(UserJID), |
164 |
23 |
Params = #{user_jid => BUserJID}, |
165 |
23 |
{ok, Rows} = mongoose_cassandra:cql_read(pool_name(HostType), UserJID, ?MODULE, |
166 |
|
get_prefs_query, Params), |
167 |
23 |
{ok, decode_prefs_rows(Rows, GlobalDefaultMode, [], [])}. |
168 |
|
|
169 |
|
|
170 |
|
-spec remove_archive(Acc, Params, Extra) -> {ok, Acc} when |
171 |
|
Acc :: term(), |
172 |
|
Params :: #{archive_id := mod_mam:archive_id() | undefined, owner => jid:jid(), room => jid:jid()}, |
173 |
|
Extra :: gen_hook:extra(). |
174 |
|
remove_archive(Acc, #{owner := UserJID}, #{host_type := HostType}) -> |
175 |
37 |
remove_archive(HostType, UserJID), |
176 |
37 |
{ok, Acc}; |
177 |
|
remove_archive(Acc, #{room := UserJID}, #{host_type := HostType}) -> |
178 |
64 |
remove_archive(HostType, UserJID), |
179 |
64 |
{ok, Acc}. |
180 |
|
|
181 |
|
remove_archive(HostType, UserJID) -> |
182 |
101 |
BUserJID = mod_mam_utils:bare_jid(UserJID), |
183 |
101 |
Now = mongoose_cassandra:now_timestamp(), |
184 |
101 |
Params = #{'[timestamp]' => Now, user_jid => BUserJID}, |
185 |
101 |
mongoose_cassandra:cql_write(pool_name(HostType), UserJID, |
186 |
|
?MODULE, del_prefs_ts_query, [Params]). |
187 |
|
|
188 |
|
-spec query_behaviour(host_type(), UserJID :: jid:jid(), BUserJID :: binary() | string(), |
189 |
|
BRemJID :: binary() | string(), BRemBareJID :: binary() | string()) -> any(). |
190 |
|
query_behaviour(HostType, UserJID, BUserJID, BRemJID, BRemBareJID) |
191 |
|
when BRemJID == BRemBareJID -> |
192 |
10 |
Params = #{user_jid => BUserJID, remote_jid => BRemBareJID}, |
193 |
10 |
mongoose_cassandra:cql_read(pool_name(HostType), UserJID, ?MODULE, |
194 |
|
get_behaviour_bare_query, Params); |
195 |
|
query_behaviour(HostType, UserJID, BUserJID, BRemJID, BRemBareJID) -> |
196 |
314 |
Params = #{user_jid => BUserJID, start_remote_jid => BRemJID, |
197 |
|
end_remote_jid => BRemBareJID}, |
198 |
314 |
mongoose_cassandra:cql_read(pool_name(HostType), UserJID, ?MODULE, |
199 |
|
get_behaviour_full_query, Params). |
200 |
|
|
201 |
|
%% ---------------------------------------------------------------------- |
202 |
|
%% Helpers |
203 |
|
|
204 |
|
-spec encode_behaviour('always' | 'never' | 'roster') -> binary(). |
205 |
16 |
encode_behaviour(roster) -> <<"R">>; |
206 |
14 |
encode_behaviour(always) -> <<"A">>; |
207 |
14 |
encode_behaviour(never) -> <<"N">>. |
208 |
|
|
209 |
|
|
210 |
|
-spec decode_behaviour(<<_:8>>) -> 'always' | 'never' | 'roster'. |
211 |
21 |
decode_behaviour(<<"R">>) -> roster; |
212 |
43 |
decode_behaviour(<<"A">>) -> always; |
213 |
43 |
decode_behaviour(<<"N">>) -> never. |
214 |
|
|
215 |
|
-spec decode_prefs_rows([[term()]], DefaultMode, AlwaysJIDs, NeverJIDs) -> |
216 |
|
{DefaultMode, AlwaysJIDs, NeverJIDs} when |
217 |
|
DefaultMode :: mod_mam:archive_behaviour(), |
218 |
|
AlwaysJIDs :: [jid:literal_jid()], |
219 |
|
NeverJIDs :: [jid:literal_jid()]. |
220 |
|
decode_prefs_rows([], DefaultMode, AlwaysJIDs, NeverJIDs) -> |
221 |
23 |
{DefaultMode, AlwaysJIDs, NeverJIDs}; |
222 |
|
|
223 |
|
decode_prefs_rows([#{remote_jid := <<>>, behaviour := Behaviour} | Rows], |
224 |
|
_DefaultMode, AlwaysJIDs, NeverJIDs) -> |
225 |
23 |
decode_prefs_rows(Rows, decode_behaviour(Behaviour), AlwaysJIDs, NeverJIDs); |
226 |
|
decode_prefs_rows([#{remote_jid := JID, behaviour := <<"A">>} | Rows], |
227 |
|
DefaultMode, AlwaysJIDs, NeverJIDs) -> |
228 |
15 |
decode_prefs_rows(Rows, DefaultMode, [JID | AlwaysJIDs], NeverJIDs); |
229 |
|
decode_prefs_rows([#{remote_jid := JID, behaviour := <<"N">>} | Rows], |
230 |
|
DefaultMode, AlwaysJIDs, NeverJIDs) -> |
231 |
13 |
decode_prefs_rows(Rows, DefaultMode, AlwaysJIDs, [JID | NeverJIDs]). |
232 |
|
|
233 |
|
%% ---------------------------------------------------------------------- |
234 |
|
%% Params getters |
235 |
|
|
236 |
|
-spec pool_name(HostType :: host_type()) -> term(). |
237 |
|
pool_name(_HostType) -> |
238 |
536 |
default. |