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