1 |
|
%%%---------------------------------------------------------------------- |
2 |
|
%%% File : mod_muc_light_db_mnesia.erl |
3 |
|
%%% Author : Piotr Nosek <piotr.nosek@erlang-solutions.com> |
4 |
|
%%% Purpose : Mnesia backend for mod_muc_light |
5 |
|
%%% Created : 8 Sep 2014 by Piotr Nosek <piotr.nosek@erlang-solutions.com> |
6 |
|
%%% |
7 |
|
%%% This program is free software; you can redistribute it and/or |
8 |
|
%%% modify it under the terms of the GNU General Public License as |
9 |
|
%%% published by the Free Software Foundation; either version 2 of the |
10 |
|
%%% License, or (at your option) any later version. |
11 |
|
%%% |
12 |
|
%%% This program is distributed in the hope that it will be useful, |
13 |
|
%%% but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 |
|
%%% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
15 |
|
%%% General Public License for more details. |
16 |
|
%%% |
17 |
|
%%% You should have received a copy of the GNU General Public License |
18 |
|
%%% along with this program; if not, write to the Free Software |
19 |
|
%%% Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
20 |
|
%%% |
21 |
|
%%%---------------------------------------------------------------------- |
22 |
|
|
23 |
|
-module(mod_muc_light_db_mnesia). |
24 |
|
-author('piotr.nosek@erlang-solutions.com'). |
25 |
|
|
26 |
|
-behaviour(mod_muc_light_db_backend). |
27 |
|
|
28 |
|
%% API |
29 |
|
-export([ |
30 |
|
start/2, |
31 |
|
stop/1, |
32 |
|
create_room/5, |
33 |
|
destroy_room/2, |
34 |
|
room_exists/2, |
35 |
|
get_user_rooms/3, |
36 |
|
get_user_rooms_count/2, |
37 |
|
remove_user/3, |
38 |
|
remove_domain/3, |
39 |
|
get_config/2, |
40 |
|
set_config/4, |
41 |
|
get_blocking/3, |
42 |
|
get_blocking/4, |
43 |
|
set_blocking/4, |
44 |
|
get_aff_users/2, |
45 |
|
modify_aff_users/5, |
46 |
|
get_info/2 |
47 |
|
]). |
48 |
|
|
49 |
|
%% Extra API for testing |
50 |
|
-export([force_clear/0]). |
51 |
|
-ignore_xref([force_clear/0]). |
52 |
|
|
53 |
|
-include("mod_muc_light.hrl"). |
54 |
|
|
55 |
|
-record(muc_light_room, { |
56 |
|
room :: jid:simple_bare_jid(), |
57 |
|
config :: [{atom(), term()}], |
58 |
|
aff_users :: aff_users(), |
59 |
|
version :: binary() |
60 |
|
}). |
61 |
|
|
62 |
|
-record(muc_light_user_room, { |
63 |
|
user :: jid:simple_bare_jid(), |
64 |
|
room :: jid:simple_bare_jid() |
65 |
|
}). |
66 |
|
|
67 |
|
-record(muc_light_blocking, { |
68 |
|
user :: jid:simple_bare_jid(), |
69 |
|
item :: {user | room, jid:simple_bare_jid()} |
70 |
|
}). |
71 |
|
|
72 |
|
-type muc_light_room() :: #muc_light_room{}. |
73 |
|
-type muc_light_blocking() :: #muc_light_blocking{}. |
74 |
|
|
75 |
|
%%==================================================================== |
76 |
|
%% API |
77 |
|
%%==================================================================== |
78 |
|
|
79 |
|
%% ------------------------ Backend start/stop ------------------------ |
80 |
|
|
81 |
|
-spec start(Host :: jid:server(), any()) -> ok. |
82 |
|
start(_Host, _) -> |
83 |
8 |
init_tables(). |
84 |
|
|
85 |
|
-spec stop(Host :: jid:server()) -> ok. |
86 |
|
stop(_Host) -> |
87 |
8 |
ok. |
88 |
|
|
89 |
|
%% ------------------------ General room management ------------------------ |
90 |
|
|
91 |
|
-spec create_room(mongooseim:host_type(), RoomUS :: jid:simple_bare_jid(), Config :: mod_muc_light_room_config:kv(), |
92 |
|
AffUsers :: aff_users(), Version :: binary()) -> |
93 |
|
{ok, FinalRoomUS :: jid:simple_bare_jid()} | {error, exists}. |
94 |
|
create_room(_HostType, RoomUS, Config, AffUsers, Version) -> |
95 |
20 |
{atomic, Res} = mnesia:transaction(fun create_room_transaction/4, |
96 |
|
[RoomUS, Config, AffUsers, Version]), |
97 |
20 |
Res. |
98 |
|
|
99 |
|
-spec destroy_room(HostType :: mongooseim:host_type(), |
100 |
|
RoomUS :: jid:simple_bare_jid()) -> |
101 |
|
ok | {error, not_exists}. |
102 |
|
destroy_room(_HostType, RoomUS) -> |
103 |
:-( |
{atomic, Res} = mnesia:transaction(fun destroy_room_transaction/1, [RoomUS]), |
104 |
:-( |
Res. |
105 |
|
|
106 |
|
-spec room_exists(HostType :: mongooseim:host_type(), |
107 |
|
RoomUS :: jid:simple_bare_jid()) -> boolean(). |
108 |
|
room_exists(_HostType, RoomUS) -> |
109 |
41 |
mnesia:dirty_read(muc_light_room, RoomUS) =/= []. |
110 |
|
|
111 |
|
-spec get_user_rooms(HostType :: mongooseim:host_type(), |
112 |
|
UserUS :: jid:simple_bare_jid(), |
113 |
|
MUCServer :: jid:lserver() | undefined) -> |
114 |
|
[RoomUS :: jid:simple_bare_jid()]. |
115 |
|
get_user_rooms(_HostType, UserUS, _MUCHost) -> |
116 |
:-( |
UsersRooms = mnesia:dirty_read(muc_light_user_room, UserUS), |
117 |
:-( |
[ UserRoom#muc_light_user_room.room || UserRoom <- UsersRooms ]. |
118 |
|
|
119 |
|
-spec get_user_rooms_count(HostType :: mongooseim:host_type(), |
120 |
|
UserUS :: jid:simple_bare_jid()) -> |
121 |
|
non_neg_integer(). |
122 |
|
get_user_rooms_count(_HostType, UserUS) -> |
123 |
:-( |
length(mnesia:dirty_read(muc_light_user_room, UserUS)). |
124 |
|
|
125 |
|
-spec remove_user(HostType :: mongooseim:host_type(), |
126 |
|
UserUS :: jid:simple_bare_jid(), |
127 |
|
Version :: binary()) -> |
128 |
|
mod_muc_light_db_backend:remove_user_return() | {error, term()}. |
129 |
|
remove_user(_HostType, UserUS, Version) -> |
130 |
29 |
mnesia:dirty_delete(muc_light_blocking, UserUS), |
131 |
29 |
{atomic, Res} = mnesia:transaction(fun remove_user_transaction/2, [UserUS, Version]), |
132 |
29 |
Res. |
133 |
|
|
134 |
|
-spec remove_domain(mongooseim:host_type(), jid:lserver(), jid:lserver()) -> ok. |
135 |
|
remove_domain(_HostType, _RoomS, _LServer) -> |
136 |
:-( |
ok. |
137 |
|
|
138 |
|
%% ------------------------ Configuration manipulation ------------------------ |
139 |
|
|
140 |
|
-spec get_config(HostType :: mongooseim:host_type(), |
141 |
|
RoomUS :: jid:simple_bare_jid()) -> |
142 |
|
{ok, mod_muc_light_room_config:kv(), Version :: binary()} | {error, not_exists}. |
143 |
|
get_config(_HostType, RoomUS) -> |
144 |
:-( |
case mnesia:dirty_read(muc_light_room, RoomUS) of |
145 |
:-( |
[] -> {error, not_exists}; |
146 |
:-( |
[#muc_light_room{ config = Config, version = Version }] -> {ok, Config, Version} |
147 |
|
end. |
148 |
|
|
149 |
|
-spec set_config(HostType :: mongooseim:host_type(), |
150 |
|
RoomUS :: jid:simple_bare_jid(), |
151 |
|
Config :: mod_muc_light_room_config:kv(), |
152 |
|
Version :: binary()) -> |
153 |
|
{ok, PrevVersion :: binary()} | {error, not_exists}. |
154 |
|
set_config(_HostType, RoomUS, ConfigChanges, Version) -> |
155 |
:-( |
{atomic, Res} = mnesia:transaction(fun set_config_transaction/3, |
156 |
|
[RoomUS, ConfigChanges, Version]), |
157 |
:-( |
Res. |
158 |
|
|
159 |
|
%% ------------------------ Blocking manipulation ------------------------ |
160 |
|
|
161 |
|
-spec get_blocking(HostType :: mongooseim:host_type(), |
162 |
|
UserUS :: jid:simple_bare_jid(), MUCServer :: jid:lserver()) -> |
163 |
|
[blocking_item()]. |
164 |
|
get_blocking(_HostType, UserUS, _MUCServer) -> |
165 |
:-( |
[ {What, deny, Who} |
166 |
:-( |
|| #muc_light_blocking{ item = {What, Who} } <- dirty_get_blocking_raw(UserUS) ]. |
167 |
|
|
168 |
|
-spec get_blocking(HostType :: mongooseim:host_type(), |
169 |
|
UserUS :: jid:simple_bare_jid(), |
170 |
|
MUCServer :: jid:lserver(), |
171 |
|
WhatWhos :: [{blocking_what(), jid:simple_bare_jid()}]) -> |
172 |
|
blocking_action(). |
173 |
|
get_blocking(_HostType, UserUS, _MUCServer, WhatWhos) -> |
174 |
25 |
Blocklist = dirty_get_blocking_raw(UserUS), |
175 |
25 |
case lists:any( |
176 |
|
fun(WhatWho) -> |
177 |
38 |
lists:keyfind(WhatWho, #muc_light_blocking.item, Blocklist) =/= false |
178 |
|
end, WhatWhos) of |
179 |
:-( |
true -> deny; |
180 |
25 |
false -> allow |
181 |
|
end. |
182 |
|
|
183 |
|
-spec set_blocking(HostType :: mongooseim:host_type(), |
184 |
|
UserUS :: jid:simple_bare_jid(), |
185 |
|
MUCServer :: jid:lserver(), |
186 |
|
BlockingItems :: [blocking_item()]) -> ok. |
187 |
|
set_blocking(_HostType, _UserUS, _MUCServer, []) -> |
188 |
:-( |
ok; |
189 |
|
set_blocking(HostType, UserUS, MUCServer, [{What, deny, Who} | RBlockingItems]) -> |
190 |
:-( |
mnesia:dirty_write(#muc_light_blocking{ user = UserUS, item = {What, Who} }), |
191 |
:-( |
set_blocking(HostType, UserUS, MUCServer, RBlockingItems); |
192 |
|
set_blocking(HostType, UserUS, MUCServer, [{What, allow, Who} | RBlockingItems]) -> |
193 |
:-( |
mnesia:dirty_delete_object(#muc_light_blocking{ user = UserUS, item = {What, Who} }), |
194 |
:-( |
set_blocking(HostType, UserUS, MUCServer, RBlockingItems). |
195 |
|
|
196 |
|
%% ------------------------ Affiliations manipulation ------------------------ |
197 |
|
|
198 |
|
-spec get_aff_users(HostType :: mongooseim:host_type(), |
199 |
|
RoomUS :: jid:simple_bare_jid()) -> |
200 |
|
{ok, aff_users(), Version :: binary()} | {error, not_exists}. |
201 |
|
get_aff_users(_HostType, RoomUS) -> |
202 |
50 |
case mnesia:dirty_read(muc_light_room, RoomUS) of |
203 |
:-( |
[] -> {error, not_exists}; |
204 |
50 |
[#muc_light_room{ aff_users = AffUsers, version = Version }] -> {ok, AffUsers, Version} |
205 |
|
end. |
206 |
|
|
207 |
|
-spec modify_aff_users(HostType :: mongooseim:host_type(), |
208 |
|
RoomUS :: jid:simple_bare_jid(), |
209 |
|
AffUsersChanges :: aff_users(), |
210 |
|
ExternalCheck :: external_check_fun(), |
211 |
|
Version :: binary()) -> |
212 |
|
mod_muc_light_db_backend:modify_aff_users_return(). |
213 |
|
modify_aff_users(_HostType, RoomUS, AffUsersChanges, ExternalCheck, Version) -> |
214 |
2 |
{atomic, Res} = mnesia:transaction(fun modify_aff_users_transaction/4, |
215 |
|
[RoomUS, AffUsersChanges, ExternalCheck, Version]), |
216 |
2 |
Res. |
217 |
|
|
218 |
|
%% ------------------------ Misc ------------------------ |
219 |
|
|
220 |
|
-spec get_info(HostType :: mongooseim:host_type(), |
221 |
|
RoomUS :: jid:simple_bare_jid()) -> |
222 |
|
{ok, mod_muc_light_room_config:kv(), aff_users(), Version :: binary()} |
223 |
|
| {error, not_exists}. |
224 |
|
get_info(_HostType, RoomUS) -> |
225 |
:-( |
case mnesia:dirty_read(muc_light_room, RoomUS) of |
226 |
|
[] -> |
227 |
:-( |
{error, not_exists}; |
228 |
|
[#muc_light_room{ config = Config, aff_users = AffUsers, version = Version }] -> |
229 |
:-( |
{ok, Config, AffUsers, Version} |
230 |
|
end. |
231 |
|
|
232 |
|
%%==================================================================== |
233 |
|
%% API for tests |
234 |
|
%%==================================================================== |
235 |
|
|
236 |
|
-spec force_clear() -> ok. |
237 |
|
force_clear() -> |
238 |
8 |
lists:foreach(fun(RoomUS) -> mod_muc_light_utils:run_forget_room_hook(RoomUS) end, |
239 |
|
mnesia:dirty_all_keys(muc_light_room)), |
240 |
8 |
lists:foreach(fun mnesia:clear_table/1, |
241 |
|
[muc_light_room, muc_light_user_room, muc_light_blocking]). |
242 |
|
|
243 |
|
%%==================================================================== |
244 |
|
%% Internal functions |
245 |
|
%%==================================================================== |
246 |
|
|
247 |
|
%% ------------------------ Schema creation ------------------------ |
248 |
|
|
249 |
|
-spec init_tables() -> ok. |
250 |
|
init_tables() -> |
251 |
8 |
mongoose_mnesia:create_table(muc_light_room, |
252 |
|
[{disc_copies, [node()]}, |
253 |
|
{attributes, record_info(fields, muc_light_room)}]), |
254 |
8 |
mongoose_mnesia:create_table(muc_light_user_room, |
255 |
|
[{disc_copies, [node()]}, {type, bag}, |
256 |
|
{attributes, record_info(fields, muc_light_user_room)}]), |
257 |
8 |
mongoose_mnesia:create_table(muc_light_blocking, |
258 |
|
[{disc_copies, [node()]}, {type, bag}, |
259 |
|
{attributes, record_info(fields, muc_light_blocking)}]), |
260 |
8 |
ok. |
261 |
|
|
262 |
|
%% ------------------------ General room management ------------------------ |
263 |
|
|
264 |
|
%% Expects config to have unique fields! |
265 |
|
-spec create_room_transaction(RoomUS :: jid:simple_bare_jid(), |
266 |
|
Config :: mod_muc_light_room_config:kv(), |
267 |
|
AffUsers :: aff_users(), |
268 |
|
Version :: binary()) -> |
269 |
|
{ok, FinalRoomUS :: jid:simple_bare_jid()} | {error, exists}. |
270 |
|
create_room_transaction({<<>>, Domain}, Config, AffUsers, Version) -> |
271 |
6 |
NodeCandidate = mongoose_bin:gen_from_timestamp(), |
272 |
6 |
NewNode = case mnesia:wread({muc_light_room, {NodeCandidate, Domain}}) of |
273 |
:-( |
[_] -> <<>>; |
274 |
6 |
[] -> NodeCandidate |
275 |
|
end, |
276 |
6 |
create_room_transaction({NewNode, Domain}, Config, AffUsers, Version); |
277 |
|
create_room_transaction(RoomUS, Config, AffUsers, Version) -> |
278 |
20 |
case mnesia:wread({muc_light_room, RoomUS}) of |
279 |
|
[_] -> |
280 |
:-( |
{error, exists}; |
281 |
|
[] -> |
282 |
20 |
RoomRecord = #muc_light_room{ |
283 |
|
room = RoomUS, |
284 |
|
config = lists:sort(Config), |
285 |
|
aff_users = AffUsers, |
286 |
|
version = Version |
287 |
|
}, |
288 |
20 |
ok = mnesia:write(RoomRecord), |
289 |
20 |
lists:foreach( |
290 |
|
fun({User, _}) -> |
291 |
43 |
UserRoomRecord = #muc_light_user_room{ |
292 |
|
user = User, |
293 |
|
room = RoomUS |
294 |
|
}, |
295 |
43 |
ok = mnesia:write(UserRoomRecord) |
296 |
|
end, AffUsers), |
297 |
20 |
{ok, RoomUS} |
298 |
|
end. |
299 |
|
|
300 |
|
-spec destroy_room_transaction(RoomUS :: jid:simple_bare_jid()) -> ok | {error, not_exists}. |
301 |
|
destroy_room_transaction(RoomUS) -> |
302 |
:-( |
case mnesia:wread({muc_light_room, RoomUS}) of |
303 |
|
[] -> |
304 |
:-( |
{error, not_exists}; |
305 |
|
[Rec] -> |
306 |
:-( |
AffUsers = Rec#muc_light_room.aff_users, |
307 |
:-( |
lists:foreach( |
308 |
|
fun({User, _}) -> |
309 |
:-( |
ok = mnesia:delete_object(#muc_light_user_room{user = User, room = RoomUS}) |
310 |
|
end, AffUsers), |
311 |
:-( |
mnesia:delete({muc_light_room, RoomUS}) |
312 |
|
end. |
313 |
|
|
314 |
|
-spec remove_user_transaction(UserUS :: jid:simple_bare_jid(), Version :: binary()) -> |
315 |
|
mod_muc_light_db_backend:remove_user_return(). |
316 |
|
remove_user_transaction(UserUS, Version) -> |
317 |
29 |
lists:map( |
318 |
|
fun(#muc_light_user_room{ room = RoomUS }) -> |
319 |
:-( |
{RoomUS, modify_aff_users_transaction( |
320 |
:-( |
RoomUS, [{UserUS, none}], fun(_, _) -> ok end, Version)} |
321 |
|
end, mnesia:read(muc_light_user_room, UserUS)). |
322 |
|
|
323 |
|
%% ------------------------ Configuration manipulation ------------------------ |
324 |
|
|
325 |
|
%% Expects config changes to have unique fields! |
326 |
|
-spec set_config_transaction(RoomUS :: jid:simple_bare_jid(), |
327 |
|
ConfigChanges :: mod_muc_light_room_config:kv(), |
328 |
|
Version :: binary()) -> |
329 |
|
{ok, PrevVersion :: binary()} | {error, not_exists}. |
330 |
|
set_config_transaction(RoomUS, ConfigChanges, Version) -> |
331 |
:-( |
case mnesia:wread({muc_light_room, RoomUS}) of |
332 |
|
[] -> |
333 |
:-( |
{error, not_exists}; |
334 |
|
[#muc_light_room{ config = Config } = Rec] -> |
335 |
:-( |
NewConfig = lists:ukeymerge(1, lists:sort(ConfigChanges), Config), |
336 |
:-( |
mnesia:write(Rec#muc_light_room{ config = NewConfig, version = Version }), |
337 |
:-( |
{ok, Rec#muc_light_room.version} |
338 |
|
end. |
339 |
|
|
340 |
|
%% ------------------------ Blocking manipulation ------------------------ |
341 |
|
|
342 |
|
-spec dirty_get_blocking_raw(UserUS :: jid:simple_bare_jid()) -> [muc_light_blocking()]. |
343 |
|
dirty_get_blocking_raw(UserUS) -> |
344 |
25 |
mnesia:dirty_read(muc_light_blocking, UserUS). |
345 |
|
|
346 |
|
%% ------------------------ Affiliations manipulation ------------------------ |
347 |
|
|
348 |
|
-spec modify_aff_users_transaction(RoomUS :: jid:simple_bare_jid(), |
349 |
|
AffUsersChanges :: aff_users(), |
350 |
|
ExternalCheck :: external_check_fun(), |
351 |
|
Version :: binary()) -> |
352 |
|
mod_muc_light_db_backend:modify_aff_users_return(). |
353 |
|
modify_aff_users_transaction(RoomUS, AffUsersChanges, ExternalCheck, Version) -> |
354 |
2 |
case mnesia:wread({muc_light_room, RoomUS}) of |
355 |
|
[] -> |
356 |
:-( |
{error, not_exists}; |
357 |
|
[#muc_light_room{ aff_users = AffUsers } = RoomRec] -> |
358 |
2 |
case mod_muc_light_utils:change_aff_users(AffUsers, AffUsersChanges) of |
359 |
|
{ok, NewAffUsers, _, _, _} = ChangeResult -> |
360 |
2 |
verify_externally_and_submit( |
361 |
|
RoomUS, RoomRec, ChangeResult, ExternalCheck(RoomUS, NewAffUsers), Version); |
362 |
|
Error -> |
363 |
:-( |
Error |
364 |
|
end |
365 |
|
end. |
366 |
|
|
367 |
|
-spec verify_externally_and_submit(RoomUS :: jid:simple_bare_jid(), |
368 |
|
RoomRec :: muc_light_room(), |
369 |
|
ChangeResult :: mod_muc_light_utils:change_aff_success(), |
370 |
|
CheckResult :: ok | {error, any()}, |
371 |
|
Version :: binary()) -> |
372 |
|
mod_muc_light_db_backend:modify_aff_users_return(). |
373 |
|
verify_externally_and_submit( |
374 |
|
RoomUS, #muc_light_room{ aff_users = OldAffUsers, version = PrevVersion } = RoomRec, |
375 |
|
{ok, NewAffUsers, AffUsersChanged, JoiningUsers, LeavingUsers}, ok, Version) -> |
376 |
2 |
ok = mnesia:write(RoomRec#muc_light_room{ aff_users = NewAffUsers, version = Version }), |
377 |
2 |
update_users_rooms(RoomUS, JoiningUsers, LeavingUsers), |
378 |
2 |
{ok, OldAffUsers, NewAffUsers, AffUsersChanged, PrevVersion}; |
379 |
|
verify_externally_and_submit(_, _, _, Error, _) -> |
380 |
:-( |
Error. |
381 |
|
|
382 |
|
-spec update_users_rooms(RoomUS :: jid:simple_bare_jid(), |
383 |
|
JoiningUsers :: [jid:simple_bare_jid()], |
384 |
|
LeavingUsers :: [jid:simple_bare_jid()]) -> ok. |
385 |
|
update_users_rooms(RoomUS, [User | RJoiningUsers], LeavingUsers) -> |
386 |
2 |
ok = mnesia:write(#muc_light_user_room{ user = User, room = RoomUS }), |
387 |
2 |
update_users_rooms(RoomUS, RJoiningUsers, LeavingUsers); |
388 |
|
update_users_rooms(RoomUS, [], [User | RLeavingUsers]) -> |
389 |
:-( |
ok = mnesia:delete_object(#muc_light_user_room{ user = User, room = RoomUS }), |
390 |
:-( |
update_users_rooms(RoomUS, [], RLeavingUsers); |
391 |
|
update_users_rooms(_RoomUS, [], []) -> |
392 |
2 |
ok. |
393 |
|
|