./ct_report/coverage/mod_muc_light_db_mnesia.COVER.html

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 17 init_tables().
84
85 -spec stop(Host :: jid:server()) -> ok.
86 stop(_Host) ->
87 17 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 17 create_table(muc_light_room,
252 [{disc_copies, [node()]},
253 {attributes, record_info(fields, muc_light_room)}]),
254 17 create_table(muc_light_user_room,
255 [{disc_copies, [node()]},
256 {attributes, record_info(fields, muc_light_user_room)},
257 {type, bag}]),
258 17 create_table(muc_light_blocking,
259 [{disc_copies, [node()]},
260 {attributes, record_info(fields, muc_light_blocking)},
261 {type, bag}]),
262 17 ok.
263
264 -spec create_table(Name :: atom(), TabDef :: list()) -> ok.
265 create_table(Name, TabDef) ->
266 51 case mnesia:create_table(Name, TabDef) of
267 6 {atomic, ok} -> ok;
268
:-(
{aborted, exists} -> ok;
269 45 {aborted, {already_exists, _}} -> ok
270 end,
271 51 case mnesia:add_table_copy(Name, node(), disc_copies) of
272
:-(
{atomic, ok} -> ok;
273
:-(
{aborted, exists} -> ok;
274 51 {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
Line Hits Source