./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 48 init_tables().
84
85 -spec stop(Host :: jid:server()) -> ok.
86 stop(_Host) ->
87 48 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 230 {atomic, Res} = mnesia:transaction(fun create_room_transaction/4,
96 [RoomUS, Config, AffUsers, Version]),
97 230 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 111 {atomic, Res} = mnesia:transaction(fun destroy_room_transaction/1, [RoomUS]),
104 111 Res.
105
106 -spec room_exists(HostType :: mongooseim:host_type(),
107 RoomUS :: jid:simple_bare_jid()) -> boolean().
108 room_exists(_HostType, RoomUS) ->
109 153 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 43 UsersRooms = mnesia:dirty_read(muc_light_user_room, UserUS),
117 43 [ 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 3 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 211 mnesia:dirty_delete(muc_light_blocking, UserUS),
131 211 {atomic, Res} = mnesia:transaction(fun remove_user_transaction/2, [UserUS, Version]),
132 211 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 48 case mnesia:dirty_read(muc_light_room, RoomUS) of
145
:-(
[] -> {error, not_exists};
146 48 [#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 27 {atomic, Res} = mnesia:transaction(fun set_config_transaction/3,
156 [RoomUS, ConfigChanges, Version]),
157 27 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 21 [ {What, deny, Who}
166 21 || #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 94 Blocklist = dirty_get_blocking_raw(UserUS),
175 94 case lists:any(
176 fun(WhatWho) ->
177 183 lists:keyfind(WhatWho, #muc_light_blocking.item, Blocklist) =/= false
178 end, WhatWhos) of
179 4 true -> deny;
180 90 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 18 ok;
189 set_blocking(HostType, UserUS, MUCServer, [{What, deny, Who} | RBlockingItems]) ->
190 14 mnesia:dirty_write(#muc_light_blocking{ user = UserUS, item = {What, Who} }),
191 14 set_blocking(HostType, UserUS, MUCServer, RBlockingItems);
192 set_blocking(HostType, UserUS, MUCServer, [{What, allow, Who} | RBlockingItems]) ->
193 9 mnesia:dirty_delete_object(#muc_light_blocking{ user = UserUS, item = {What, Who} }),
194 9 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 263 case mnesia:dirty_read(muc_light_room, RoomUS) of
203 16 [] -> {error, not_exists};
204 247 [#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 63 {atomic, Res} = mnesia:transaction(fun modify_aff_users_transaction/4,
215 [RoomUS, AffUsersChanges, ExternalCheck, Version]),
216 63 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 44 case mnesia:dirty_read(muc_light_room, RoomUS) of
226 [] ->
227 9 {error, not_exists};
228 [#muc_light_room{ config = Config, aff_users = AffUsers, version = Version }] ->
229 35 {ok, Config, AffUsers, Version}
230 end.
231
232 %%====================================================================
233 %% API for tests
234 %%====================================================================
235
236 -spec force_clear() -> ok.
237 force_clear() ->
238 83 lists:foreach(fun(RoomUS) -> mod_muc_light_utils:run_forget_room_hook(RoomUS) end,
239 mnesia:dirty_all_keys(muc_light_room)),
240 83 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 48 mongoose_mnesia:create_table(muc_light_room,
252 [{disc_copies, [node()]},
253 {attributes, record_info(fields, muc_light_room)}]),
254 48 mongoose_mnesia:create_table(muc_light_user_room,
255 [{disc_copies, [node()]}, {type, bag},
256 {attributes, record_info(fields, muc_light_user_room)}]),
257 48 mongoose_mnesia:create_table(muc_light_blocking,
258 [{disc_copies, [node()]}, {type, bag},
259 {attributes, record_info(fields, muc_light_blocking)}]),
260 48 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 82 NodeCandidate = mongoose_bin:gen_from_timestamp(),
272 82 NewNode = case mnesia:wread({muc_light_room, {NodeCandidate, Domain}}) of
273
:-(
[_] -> <<>>;
274 82 [] -> NodeCandidate
275 end,
276 82 create_room_transaction({NewNode, Domain}, Config, AffUsers, Version);
277 create_room_transaction(RoomUS, Config, AffUsers, Version) ->
278 230 case mnesia:wread({muc_light_room, RoomUS}) of
279 [_] ->
280 7 {error, exists};
281 [] ->
282 223 RoomRecord = #muc_light_room{
283 room = RoomUS,
284 config = lists:sort(Config),
285 aff_users = AffUsers,
286 version = Version
287 },
288 223 ok = mnesia:write(RoomRecord),
289 223 lists:foreach(
290 fun({User, _}) ->
291 428 UserRoomRecord = #muc_light_user_room{
292 user = User,
293 room = RoomUS
294 },
295 428 ok = mnesia:write(UserRoomRecord)
296 end, AffUsers),
297 223 {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 111 case mnesia:wread({muc_light_room, RoomUS}) of
303 [] ->
304 7 {error, not_exists};
305 [Rec] ->
306 104 AffUsers = Rec#muc_light_room.aff_users,
307 104 lists:foreach(
308 fun({User, _}) ->
309 19 ok = mnesia:delete_object(#muc_light_user_room{user = User, room = RoomUS})
310 end, AffUsers),
311 104 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 211 lists:map(
318 fun(#muc_light_user_room{ room = RoomUS }) ->
319 119 {RoomUS, modify_aff_users_transaction(
320 119 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 27 case mnesia:wread({muc_light_room, RoomUS}) of
332 [] ->
333
:-(
{error, not_exists};
334 [#muc_light_room{ config = Config } = Rec] ->
335 27 NewConfig = lists:ukeymerge(1, lists:sort(ConfigChanges), Config),
336 27 mnesia:write(Rec#muc_light_room{ config = NewConfig, version = Version }),
337 27 {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 115 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 182 case mnesia:wread({muc_light_room, RoomUS}) of
355 [] ->
356
:-(
{error, not_exists};
357 [#muc_light_room{ aff_users = AffUsers } = RoomRec] ->
358 182 case mod_muc_light_utils:change_aff_users(AffUsers, AffUsersChanges) of
359 {ok, NewAffUsers, _, _, _} = ChangeResult ->
360 181 verify_externally_and_submit(
361 RoomUS, RoomRec, ChangeResult, ExternalCheck(RoomUS, NewAffUsers), Version);
362 Error ->
363 1 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 180 ok = mnesia:write(RoomRec#muc_light_room{ aff_users = NewAffUsers, version = Version }),
377 180 update_users_rooms(RoomUS, JoiningUsers, LeavingUsers),
378 180 {ok, OldAffUsers, NewAffUsers, AffUsersChanged, PrevVersion};
379 verify_externally_and_submit(_, _, _, Error, _) ->
380 1 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 38 ok = mnesia:write(#muc_light_user_room{ user = User, room = RoomUS }),
387 38 update_users_rooms(RoomUS, RJoiningUsers, LeavingUsers);
388 update_users_rooms(RoomUS, [], [User | RLeavingUsers]) ->
389 150 ok = mnesia:delete_object(#muc_light_user_room{ user = User, room = RoomUS }),
390 150 update_users_rooms(RoomUS, [], RLeavingUsers);
391 update_users_rooms(_RoomUS, [], []) ->
392 180 ok.
393
Line Hits Source