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