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 |
16 |
init_tables(). |
84 |
|
|
85 |
|
-spec stop(Host :: jid:server()) -> ok. |
86 |
|
stop(_Host) -> |
87 |
16 |
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 |
102 |
{atomic, Res} = mnesia:transaction(fun create_room_transaction/4, |
96 |
|
[RoomUS, Config, AffUsers, Version]), |
97 |
102 |
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 |
63 |
{atomic, Res} = mnesia:transaction(fun destroy_room_transaction/1, [RoomUS]), |
104 |
63 |
Res. |
105 |
|
|
106 |
|
-spec room_exists(HostType :: mongooseim:host_type(), |
107 |
|
RoomUS :: jid:simple_bare_jid()) -> boolean(). |
108 |
|
room_exists(_HostType, RoomUS) -> |
109 |
135 |
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 |
46 |
UsersRooms = mnesia:dirty_read(muc_light_user_room, UserUS), |
117 |
46 |
[ 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 |
141 |
mnesia:dirty_delete(muc_light_blocking, UserUS), |
131 |
141 |
{atomic, Res} = mnesia:transaction(fun remove_user_transaction/2, [UserUS, Version]), |
132 |
141 |
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 |
2 |
case mnesia:dirty_read(muc_light_room, RoomUS) of |
145 |
:-( |
[] -> {error, not_exists}; |
146 |
2 |
[#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 |
4 |
{atomic, Res} = mnesia:transaction(fun set_config_transaction/3, |
156 |
|
[RoomUS, ConfigChanges, Version]), |
157 |
4 |
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 |
6 |
[ {What, deny, Who} |
166 |
6 |
|| #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 |
72 |
Blocklist = dirty_get_blocking_raw(UserUS), |
175 |
72 |
case lists:any( |
176 |
|
fun(WhatWho) -> |
177 |
138 |
lists:keyfind(WhatWho, #muc_light_blocking.item, Blocklist) =/= false |
178 |
|
end, WhatWhos) of |
179 |
:-( |
true -> deny; |
180 |
72 |
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 |
4 |
ok; |
189 |
|
set_blocking(HostType, UserUS, MUCServer, [{What, deny, Who} | RBlockingItems]) -> |
190 |
3 |
mnesia:dirty_write(#muc_light_blocking{ user = UserUS, item = {What, Who} }), |
191 |
3 |
set_blocking(HostType, UserUS, MUCServer, RBlockingItems); |
192 |
|
set_blocking(HostType, UserUS, MUCServer, [{What, allow, Who} | RBlockingItems]) -> |
193 |
2 |
mnesia:dirty_delete_object(#muc_light_blocking{ user = UserUS, item = {What, Who} }), |
194 |
2 |
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 |
184 |
case mnesia:dirty_read(muc_light_room, RoomUS) of |
203 |
5 |
[] -> {error, not_exists}; |
204 |
179 |
[#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 |
42 |
{atomic, Res} = mnesia:transaction(fun modify_aff_users_transaction/4, |
215 |
|
[RoomUS, AffUsersChanges, ExternalCheck, Version]), |
216 |
42 |
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 |
78 |
case mnesia:dirty_read(muc_light_room, RoomUS) of |
226 |
|
[] -> |
227 |
8 |
{error, not_exists}; |
228 |
|
[#muc_light_room{ config = Config, aff_users = AffUsers, version = Version }] -> |
229 |
70 |
{ok, Config, AffUsers, Version} |
230 |
|
end. |
231 |
|
|
232 |
|
%%==================================================================== |
233 |
|
%% API for tests |
234 |
|
%%==================================================================== |
235 |
|
|
236 |
|
-spec force_clear() -> ok. |
237 |
|
force_clear() -> |
238 |
10 |
lists:foreach(fun(RoomUS) -> mod_muc_light_utils:run_forget_room_hook(RoomUS) end, |
239 |
|
mnesia:dirty_all_keys(muc_light_room)), |
240 |
10 |
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 |
16 |
create_table(muc_light_room, |
252 |
|
[{disc_copies, [node()]}, |
253 |
|
{attributes, record_info(fields, muc_light_room)}]), |
254 |
16 |
create_table(muc_light_user_room, |
255 |
|
[{disc_copies, [node()]}, |
256 |
|
{attributes, record_info(fields, muc_light_user_room)}, |
257 |
|
{type, bag}]), |
258 |
16 |
create_table(muc_light_blocking, |
259 |
|
[{disc_copies, [node()]}, |
260 |
|
{attributes, record_info(fields, muc_light_blocking)}, |
261 |
|
{type, bag}]), |
262 |
16 |
ok. |
263 |
|
|
264 |
|
-spec create_table(Name :: atom(), TabDef :: list()) -> ok. |
265 |
|
create_table(Name, TabDef) -> |
266 |
48 |
case mnesia:create_table(Name, TabDef) of |
267 |
9 |
{atomic, ok} -> ok; |
268 |
:-( |
{aborted, exists} -> ok; |
269 |
39 |
{aborted, {already_exists, _}} -> ok |
270 |
|
end, |
271 |
48 |
case mnesia:add_table_copy(Name, node(), disc_copies) of |
272 |
:-( |
{atomic, ok} -> ok; |
273 |
:-( |
{aborted, exists} -> ok; |
274 |
48 |
{aborted, {already_exists, _, _}} -> ok |
275 |
|
end. |
276 |
|
|
277 |
|
%% ------------------------ General room management ------------------------ |
278 |
|
|
279 |
|
%% Expects config to have unique fields! |
280 |
|
-spec create_room_transaction(RoomUS :: jid:simple_bare_jid(), |
281 |
|
Config :: mod_muc_light_room_config:kv(), |
282 |
|
AffUsers :: aff_users(), |
283 |
|
Version :: binary()) -> |
284 |
|
{ok, FinalRoomUS :: jid:simple_bare_jid()} | {error, exists}. |
285 |
|
create_room_transaction({<<>>, Domain}, Config, AffUsers, Version) -> |
286 |
59 |
NodeCandidate = mongoose_bin:gen_from_timestamp(), |
287 |
59 |
NewNode = case mnesia:wread({muc_light_room, {NodeCandidate, Domain}}) of |
288 |
:-( |
[_] -> <<>>; |
289 |
59 |
[] -> NodeCandidate |
290 |
|
end, |
291 |
59 |
create_room_transaction({NewNode, Domain}, Config, AffUsers, Version); |
292 |
|
create_room_transaction(RoomUS, Config, AffUsers, Version) -> |
293 |
102 |
case mnesia:wread({muc_light_room, RoomUS}) of |
294 |
|
[_] -> |
295 |
2 |
{error, exists}; |
296 |
|
[] -> |
297 |
100 |
RoomRecord = #muc_light_room{ |
298 |
|
room = RoomUS, |
299 |
|
config = lists:sort(Config), |
300 |
|
aff_users = AffUsers, |
301 |
|
version = Version |
302 |
|
}, |
303 |
100 |
ok = mnesia:write(RoomRecord), |
304 |
100 |
lists:foreach( |
305 |
|
fun({User, _}) -> |
306 |
134 |
UserRoomRecord = #muc_light_user_room{ |
307 |
|
user = User, |
308 |
|
room = RoomUS |
309 |
|
}, |
310 |
134 |
ok = mnesia:write(UserRoomRecord) |
311 |
|
end, AffUsers), |
312 |
100 |
{ok, RoomUS} |
313 |
|
end. |
314 |
|
|
315 |
|
-spec destroy_room_transaction(RoomUS :: jid:simple_bare_jid()) -> ok | {error, not_exists}. |
316 |
|
destroy_room_transaction(RoomUS) -> |
317 |
63 |
case mnesia:wread({muc_light_room, RoomUS}) of |
318 |
|
[] -> |
319 |
1 |
{error, not_exists}; |
320 |
|
[Rec] -> |
321 |
62 |
AffUsers = Rec#muc_light_room.aff_users, |
322 |
62 |
lists:foreach( |
323 |
|
fun({User, _}) -> |
324 |
3 |
ok = mnesia:delete_object(#muc_light_user_room{user = User, room = RoomUS}) |
325 |
|
end, AffUsers), |
326 |
62 |
mnesia:delete({muc_light_room, RoomUS}) |
327 |
|
end. |
328 |
|
|
329 |
|
-spec remove_user_transaction(UserUS :: jid:simple_bare_jid(), Version :: binary()) -> |
330 |
|
mod_muc_light_db_backend:remove_user_return(). |
331 |
|
remove_user_transaction(UserUS, Version) -> |
332 |
141 |
lists:map( |
333 |
|
fun(#muc_light_user_room{ room = RoomUS }) -> |
334 |
79 |
{RoomUS, modify_aff_users_transaction( |
335 |
79 |
RoomUS, [{UserUS, none}], fun(_, _) -> ok end, Version)} |
336 |
|
end, mnesia:read(muc_light_user_room, UserUS)). |
337 |
|
|
338 |
|
%% ------------------------ Configuration manipulation ------------------------ |
339 |
|
|
340 |
|
%% Expects config changes to have unique fields! |
341 |
|
-spec set_config_transaction(RoomUS :: jid:simple_bare_jid(), |
342 |
|
ConfigChanges :: mod_muc_light_room_config:kv(), |
343 |
|
Version :: binary()) -> |
344 |
|
{ok, PrevVersion :: binary()} | {error, not_exists}. |
345 |
|
set_config_transaction(RoomUS, ConfigChanges, Version) -> |
346 |
4 |
case mnesia:wread({muc_light_room, RoomUS}) of |
347 |
|
[] -> |
348 |
:-( |
{error, not_exists}; |
349 |
|
[#muc_light_room{ config = Config } = Rec] -> |
350 |
4 |
NewConfig = lists:ukeymerge(1, lists:sort(ConfigChanges), Config), |
351 |
4 |
mnesia:write(Rec#muc_light_room{ config = NewConfig, version = Version }), |
352 |
4 |
{ok, Rec#muc_light_room.version} |
353 |
|
end. |
354 |
|
|
355 |
|
%% ------------------------ Blocking manipulation ------------------------ |
356 |
|
|
357 |
|
-spec dirty_get_blocking_raw(UserUS :: jid:simple_bare_jid()) -> [muc_light_blocking()]. |
358 |
|
dirty_get_blocking_raw(UserUS) -> |
359 |
78 |
mnesia:dirty_read(muc_light_blocking, UserUS). |
360 |
|
|
361 |
|
%% ------------------------ Affiliations manipulation ------------------------ |
362 |
|
|
363 |
|
-spec modify_aff_users_transaction(RoomUS :: jid:simple_bare_jid(), |
364 |
|
AffUsersChanges :: aff_users(), |
365 |
|
ExternalCheck :: external_check_fun(), |
366 |
|
Version :: binary()) -> |
367 |
|
mod_muc_light_db_backend:modify_aff_users_return(). |
368 |
|
modify_aff_users_transaction(RoomUS, AffUsersChanges, ExternalCheck, Version) -> |
369 |
121 |
case mnesia:wread({muc_light_room, RoomUS}) of |
370 |
|
[] -> |
371 |
:-( |
{error, not_exists}; |
372 |
|
[#muc_light_room{ aff_users = AffUsers } = RoomRec] -> |
373 |
121 |
case mod_muc_light_utils:change_aff_users(AffUsers, AffUsersChanges) of |
374 |
|
{ok, NewAffUsers, _, _, _} = ChangeResult -> |
375 |
120 |
verify_externally_and_submit( |
376 |
|
RoomUS, RoomRec, ChangeResult, ExternalCheck(RoomUS, NewAffUsers), Version); |
377 |
|
Error -> |
378 |
1 |
Error |
379 |
|
end |
380 |
|
end. |
381 |
|
|
382 |
|
-spec verify_externally_and_submit(RoomUS :: jid:simple_bare_jid(), |
383 |
|
RoomRec :: muc_light_room(), |
384 |
|
ChangeResult :: mod_muc_light_utils:change_aff_success(), |
385 |
|
CheckResult :: ok | {error, any()}, |
386 |
|
Version :: binary()) -> |
387 |
|
mod_muc_light_db_backend:modify_aff_users_return(). |
388 |
|
verify_externally_and_submit( |
389 |
|
RoomUS, #muc_light_room{ aff_users = OldAffUsers, version = PrevVersion } = RoomRec, |
390 |
|
{ok, NewAffUsers, AffUsersChanged, JoiningUsers, LeavingUsers}, ok, Version) -> |
391 |
120 |
ok = mnesia:write(RoomRec#muc_light_room{ aff_users = NewAffUsers, version = Version }), |
392 |
120 |
update_users_rooms(RoomUS, JoiningUsers, LeavingUsers), |
393 |
120 |
{ok, OldAffUsers, NewAffUsers, AffUsersChanged, PrevVersion}; |
394 |
|
verify_externally_and_submit(_, _, _, Error, _) -> |
395 |
:-( |
Error. |
396 |
|
|
397 |
|
-spec update_users_rooms(RoomUS :: jid:simple_bare_jid(), |
398 |
|
JoiningUsers :: [jid:simple_bare_jid()], |
399 |
|
LeavingUsers :: [jid:simple_bare_jid()]) -> ok. |
400 |
|
update_users_rooms(RoomUS, [User | RJoiningUsers], LeavingUsers) -> |
401 |
34 |
ok = mnesia:write(#muc_light_user_room{ user = User, room = RoomUS }), |
402 |
34 |
update_users_rooms(RoomUS, RJoiningUsers, LeavingUsers); |
403 |
|
update_users_rooms(RoomUS, [], [User | RLeavingUsers]) -> |
404 |
86 |
ok = mnesia:delete_object(#muc_light_user_room{ user = User, room = RoomUS }), |
405 |
86 |
update_users_rooms(RoomUS, [], RLeavingUsers); |
406 |
|
update_users_rooms(_RoomUS, [], []) -> |
407 |
120 |
ok. |
408 |
|
|