Update to the new library 'json2'
[euphorik.git] / modules / erl / euphorik_bd.erl
1 % coding: utf-8
2 % Copyright 2008 Grégory Burri
3 %
4 % This file is part of Euphorik.
5 %
6 % Euphorik is free software: you can redistribute it and/or modify
7 % it under the terms of the GNU General Public License as published by
8 % the Free Software Foundation, either version 3 of the License, or
9 % (at your option) any later version.
10 %
11 % Euphorik is distributed in the hope that it will be useful,
12 % but WITHOUT ANY WARRANTY; without even the implied warranty of
13 % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 % GNU General Public License for more details.
15 %
16 % You should have received a copy of the GNU General Public License
17 % along with Euphorik. If not, see <http://www.gnu.org/licenses/>.
18 %
19 % Ce module permet de gérer les données persistantes lié au site d'euphorik.ch.
20 % Il permet d'ajouter des message, de demande les messages sur une page donnée, etc..
21 % Ce module utilise une base mnesia.
22
23
24 -module(euphorik_bd).
25 -author("Greg Burri <greg.burri@gmail.com>").
26 -export([
27 % texte :
28 get_texte/1,
29 get_texte/2,
30
31 % users :
32 nouveau_user/2,
33 nouveau_user/4,
34 set_profile/4,
35 update_date_derniere_connexion/1,
36 update_ip/2,
37 update_pseudo_user/2,
38 user_by_cookie/1,
39 user_by_id/1,
40 user_by_login/1,
41 user_by_login_password/2,
42 user_by_mess/1,
43 css_from_user_cookie/1,
44 is_ek_master_from_cookie/1,
45
46 % messages :
47 nouveau_message/3,
48 nouveau_message_sys/1,
49 messages/1,
50 messages/2,
51 messages/3,
52 message_by_id/1,
53 messages_by_ids/1,
54 message_existe/1,
55 reponses/0,
56 parents/1,
57 enfants/1,
58 parents_id/1,
59 enfants_id/1,
60 est_une_reponse_a_user/2,
61 a_repondu_a_message/2,
62 possede_message/2,
63
64 % ip :
65 list_ban/0,
66 ban/2,
67 deban/1,
68 est_banni/1,
69 can_register/1,
70
71 % utiles :
72 resultat_transaction/1,
73 get_tuples/3 % must be in a transaction
74 ]).
75 -import(qlc, [e/2, q/1, cursor/2]).
76 -include("../include/euphorik_bd.hrl").
77 -include("../include/euphorik_defines.hrl").
78 -include_lib("stdlib/include/qlc.hrl").
79
80
81 get_texte(Id) ->
82 get_texte(Id, fr).
83
84
85 % TODO : généraliser la langue
86 get_texte(Id, _Lang = fr) ->
87 resultat_transaction(mnesia:transaction(fun() ->
88 case mnesia:read({texte, Id}) of
89 [#texte{fr = Texte}] -> Texte;
90 _ -> "Message " ++ integer_to_list(Id) ++ " unknown"
91 end
92 end)).
93
94
95 % Ajoute un nouveau user et le renvoie
96 nouveau_user(Cookie, Profile) ->
97 F = fun() ->
98 Id = nouvel_id(user),
99 User = #user{id = Id, cookie = Cookie, date_creation = erlang:timestamp(), date_derniere_connexion = erlang:timestamp(), profile = Profile},
100 mnesia:write(User),
101 User
102 end,
103 resultat_transaction(mnesia:transaction(F)).
104
105
106 % Ajoute un nouveau user et le renvoie
107 nouveau_user(Login, Password, Cookie, Profile) ->
108 F = fun() ->
109 Id = nouvel_id(user),
110 User = #user{id = Id, cookie = Cookie, login = Login, password = Password, date_creation = erlang:timestamp(), date_derniere_connexion = erlang:timestamp(), profile = Profile#profile{pseudo = Login}},
111 mnesia:write(User),
112 User
113 end,
114 resultat_transaction(mnesia:transaction(F)).
115
116
117 % Définit les données du profile d'une utilisateur.
118 set_profile(Cookie, Login, Password, Profile) ->
119 resultat_transaction(mnesia:transaction(
120 fun() ->
121 case user_by_cookie(Cookie) of
122 {ok, User_existant} ->
123 case user_by_login(Login) of
124 {ok, U} when Login =/= [], U#user.id =/= User_existant#user.id ->
125 login_deja_pris;
126 _ ->
127 mnesia:write(
128 User_existant#user{
129 login = if User_existant#user.login =:= [] -> Login; true -> User_existant#user.login end, % on ne peut pas changer de login sauf si on en a pas !
130 password = if Password =:= [] -> User_existant#user.password; true -> Password end,
131 profile = Profile
132 }
133 ),
134 ok
135 end;
136 _ -> erreur
137 end
138 end
139 )).
140
141
142 % Met à jour la date de la dernière connexion d'un utilisateur à maintenant
143 update_date_derniere_connexion(User_id) ->
144 mnesia:transaction(
145 fun() ->
146 case mnesia:wread({user, User_id}) of
147 [User] ->
148 mnesia:write(User#user{date_derniere_connexion = erlang:timestamp()});
149 _ ->
150 mnesia:abort("update_date_derniere_connexion: User inconnu")
151 end
152 end
153 ).
154
155
156 % Met à jour l'ip d'un user
157 update_ip(User_id, IP) ->
158 mnesia:transaction(
159 fun() ->
160 case mnesia:wread({user, User_id}) of
161 [User] ->
162 mnesia:write(User#user{last_ip = IP});
163 _ ->
164 mnesia:abort("update_ip: User inconnu")
165 end
166 end
167 ).
168
169
170 % Met à jour le pseudo du user
171 update_pseudo_user(UserId, Pseudo) ->
172 mnesia:transaction(
173 fun() ->
174 case mnesia:wread({user, UserId}) of
175 [#user{profile = Profile} = User] when Profile#profile.pseudo =/= Pseudo ->
176 mnesia:write(User#user{profile = Profile#profile { pseudo = Pseudo } });
177 _ ->
178 mnesia:abort("update_pseudo_user: User inconnu ou pseudo deja à jour")
179 end
180 end
181 ).
182
183
184 % Est-ce qu'un utilisateur existe en fonction de son cookie ?
185 % Renvoie {ok, User} ou erreur
186 user_by_cookie(Cookie) ->
187 resultat_transaction(mnesia:transaction(
188 fun() ->
189 case mnesia:index_read(user, Cookie, #user.cookie) of
190 [User] -> {ok, User};
191 _ -> erreur
192 end
193 end
194 )).
195
196
197 user_by_id(ID) ->
198 resultat_transaction(mnesia:transaction(
199 fun() ->
200 % case e(q([E || E <- mnesia:table(user), E#user.id =:= ID]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
201 % [User] -> {ok, User};
202 % _ -> erreur
203 % end
204 case mnesia:read({user, ID}) of
205 [User] -> {ok, User};
206 _ -> erreur
207 end
208 end
209 )).
210
211
212 user_by_login(Login) ->
213 resultat_transaction(mnesia:transaction(
214 fun() ->
215 Users = e(q([E || E <- mnesia:table(user), E#user.login =:= Login]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]),
216 case Users of
217 [User] -> {ok, User};
218 _ -> erreur
219 end
220 end
221 )).
222
223
224 % Renvoie une chaine représentant le cookie ou undefined si pas trouvé.
225 css_from_user_cookie(Cookie) ->
226 case user_by_cookie(Cookie) of
227 {ok, #user{profile = Profile}} ->
228 Profile#profile.css;
229 _ ->
230 undefined
231 end.
232
233
234 is_ek_master_from_cookie(Cookie) ->
235 case user_by_cookie(Cookie) of
236 {ok, #user{ek_master = true}} -> true;
237 _ -> false
238 end.
239
240
241 user_by_login_password(Login, Password) ->
242 resultat_transaction(mnesia:transaction(
243 fun() ->
244 case e(q([E || E <- mnesia:table(user), E#user.login =:= Login, E#user.password =:= Password]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
245 [User | _] -> {ok, User};
246 _ -> erreur
247 end
248 end
249 )).
250
251
252 % Renvoie {ok, User} où User est un #user possédant le message donné.
253 user_by_mess(Id) ->
254 resultat_transaction(mnesia:transaction(
255 fun() ->
256 case e(q([U || U <- mnesia:table(user), M <- mnesia:table(minichat), M#minichat.id =:= Id, M#minichat.auteur_id =:= U#user.id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
257 [User | _] -> {ok, User};
258 _ -> erreur
259 end
260 end
261 )).
262
263
264 % Ajoute un message. Repond_A est une liste d'id auquel le message répond
265 % retourne soit l'id du message soit {erreur, <raison>}.
266 nouveau_message(Mess, Auteur_id, Repond_A_ids) ->
267 % regarde si les id 'Repond_A' existent
268 F = fun() ->
269 Repond_a = lists:foldr(
270 fun(Repond_a_id, Acc) ->
271 case mnesia:read({minichat, Repond_a_id}) of
272 [M] -> [M | Acc];
273 _ -> Acc % le message n'est pas trouvé
274 end
275 end,
276 [],
277 Repond_A_ids
278 ),
279 Racine_id = case Repond_a of
280 [] -> undefined;
281 [M | _] ->
282 Une_racine = M#minichat.racine_id,
283 % vérification que tout les messages de Repond_a possède la même racine (même conversation)
284 case lists:all(fun(R) -> R#minichat.racine_id =:= Une_racine end, Repond_a) of
285 true ->
286 Une_racine;
287 _ ->
288 {erreur, "Les messages ne font pas partie de la même conversation"}
289 end
290 end,
291 if length(Repond_a) =/= length(Repond_A_ids) ->
292 {erreur, "Un ou plusieurs messages introuvable"};
293 true ->
294 case Racine_id of
295 {erreur, E} -> {erreur, E};
296 _ ->
297 % est-ce que l'auteur existe ?
298 case mnesia:wread({user, Auteur_id}) of
299 [#user{profile = Profile} = Auteur] ->
300 % comparaison entre la date du dernier poste et maintenant (gestion du flood)
301 Now = erlang:timestamp(),
302 Delta = euphorik_common:delta_date_ms(Auteur#user.date_derniere_connexion, Now),
303 Nouvel_indice_flood = Auteur#user.indice_flood + if Delta =< ?DUREE_SPAM -> 2; true -> -1 end,
304 Auteur_maj = Auteur#user{
305 indice_flood = if Nouvel_indice_flood > ?INDICE_SPAM_MAX -> ?INDICE_SPAM_MAX; Nouvel_indice_flood < 0 -> 0; true -> Nouvel_indice_flood end,
306 date_derniere_connexion = Now
307 },
308 % est-ce que l'auteur à trop floodé ?
309 if Auteur#user.indice_flood =/= ?INDICE_SPAM_MAX, Auteur_maj#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM ->
310 mnesia:write(Auteur#user{indice_flood = Auteur_maj#user.indice_flood}),
311 nouveau_message_sys("\"" ++ Profile#profile.pseudo ++ if Auteur#user.login =/= [] -> " (" ++ Auteur#user.login ++ ")"; true -> "" end ++ "\" est bloqué pour " ++ integer_to_list(trunc(?DUREE_BLOCAGE_SPAM / 1000)) ++ " secondes pour cause de flood.");
312 Auteur#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM ->
313 {erreur, "Bloqué pour cause de flood"};
314 true ->
315 mnesia:write(Auteur_maj),
316 Id = nouvel_id(minichat),
317 inserer_reponses(Id, Repond_A_ids),
318 mnesia:write(#minichat{
319 id = Id,
320 auteur_id = Auteur#user.id,
321 date = Now,
322 pseudo = Profile#profile.pseudo,
323 contenu = Mess,
324 racine_id = if Racine_id =:= undefined -> Id; true -> Racine_id end
325 }),
326 Id
327 end;
328 _ ->
329 {erreur, "L'auteur du message est introuvable"}
330 end
331 end
332 end
333 end,
334 resultat_transaction(mnesia:transaction(F)).
335
336 % Définit Id_repondant comme étant la réponse à Ids. Ids est une liste d'id.
337 inserer_reponses(Id_repondant, [Id_mess | Reste]) ->
338 mnesia:write(#reponse_minichat{repondant = Id_repondant, cible = Id_mess}),
339 inserer_reponses(Id_repondant, Reste);
340 inserer_reponses(_, []) ->
341 ok.
342
343
344 % Permet de créer un message système.
345 % Renvoie l'id du message système
346 nouveau_message_sys(Mess) ->
347 {ok, #user{profile = Profile}} = user_by_id(0),
348 resultat_transaction(mnesia:transaction(
349 fun() ->
350 Id = nouvel_id(minichat),
351 mnesia:write(#minichat{id = Id, auteur_id = 0, date = erlang:timestamp(), pseudo = Profile#profile.pseudo, contenu = Mess, racine_id = Id}),
352 Id
353 end
354 )).
355
356
357 % Renvoie N messages se trouvant sur la première page
358 messages(N) ->
359 messages(N, 1).
360
361
362 % Renvoie N messages se trouvant sur la page P
363 % TODO FIXME : fonction en O(N * P) !
364 messages(N, P) ->
365 F = fun() ->
366 get_tuples(minichat, mnesia:table_info(minichat, size) - N * P + 1, N)
367 end,
368 resultat_transaction(mnesia:transaction(F)).
369
370
371 get_tuples(Table, First_id, N) ->
372 lists:foldr(
373 fun(Id, Acc) ->
374 case mnesia:read({Table, Id}) of
375 [T] -> [T | Acc];
376 _ -> Acc
377 end
378 end,
379 [],
380 lists:seq(First_id, First_id + N - 1)
381 ).
382
383
384 % Renvoie les messages manquants pour la page P en sachant qu'il y a N message
385 % par page et que le dernier message que l'on possède est Id
386 messages(Id, N, P) ->
387 lists:filter(fun (M) -> M#minichat.id > Id end, messages(N, P)).
388
389
390 % Renvoie {ok, #minichat} (voir #minichat de euphorik_bd.hrl) à partir de son id.
391 message_by_id(Id) ->
392 resultat_transaction(mnesia:transaction(
393 fun() ->
394 case mnesia:read({minichat, Id}) of
395 [M] -> {ok, M};
396 _ -> erreur
397 end
398 end
399 )).
400
401 % Renvoie une liste de message (voir #minichat de euphorik_bd.hrl) à partir d'une liste d'id (Ids).
402 % TODO : optimisations ? serait-ce du O(n) ?
403 % Bon de toutes façons on s'en fout c'est pas utilisé :)
404 messages_by_ids(Ids) ->
405 resultat_transaction(mnesia:transaction(
406 fun() ->
407 e(qlc:keysort(
408 #minichat.id,
409 q([E || E <- mnesia:table(minichat), lists:any(fun(Id) -> Id =:= E#minichat.id end, Ids)]),
410 [{order, ascending}]
411 ),[{tmpdir, ?KEY_SORT_TEMP_DIR}])
412 end
413 )).
414
415
416 % Est-ce qu'un message existe ? Renvoie un boolean.
417 % TODO : ya pas plus simple ?
418 message_existe(Id) ->
419 resultat_transaction(mnesia:transaction(fun() ->
420 length(e(q([E#minichat.id || E <- mnesia:table(minichat), E#minichat.id =:= Id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])) =:= 1
421 end)).
422
423
424 % Renvoie les reponses (utilisé normalement uniquement pendant le debug).
425 reponses() ->
426 F = fun() ->
427 e(q([E || E <- mnesia:table(reponse_minichat)]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
428 end,
429 resultat_transaction(mnesia:transaction(F)).
430
431
432 % Renvoie les messages auquel M_id répond.
433 parents(M_id) ->
434 resultat_transaction(mnesia:transaction(
435 fun() ->
436 e(q(
437 [M || R <- mnesia:table(reponse_minichat),
438 M <- mnesia:table(minichat),
439 R#reponse_minichat.repondant =:= M_id,
440 M#minichat.id =:= R#reponse_minichat.cible]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
441 end
442 )).
443
444
445 % Renvoie les message qui repondent à M_id
446 enfants(M_id) ->
447 resultat_transaction(mnesia:transaction(
448 fun() ->
449 e(q(
450 [M || R <- mnesia:table(reponse_minichat),
451 M <- mnesia:table(minichat),
452 R#reponse_minichat.cible =:= M_id,
453 M#minichat.id =:= R#reponse_minichat.repondant]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
454 end
455 )).
456
457
458 % Renvoie les id des parents d'un message M (les messages auquels répond M)
459 % ordrés du plus petit au plus grand..
460 % On évite d'utiliser qlc pour des raisons de performance
461 % @spec parents_id(integer()) -> [integer()]
462 parents_id(M_id) ->
463 resultat_transaction(mnesia:transaction(fun() ->
464 case mnesia:read({reponse_minichat, M_id}) of
465 Parents when is_list(Parents) ->
466 lists:sort(lists:map(
467 fun(#reponse_minichat{cible = Cible}) -> Cible end,
468 Parents
469 ));
470 _ -> []
471 end
472 end)).
473
474
475 % Renvoie les id des enfants d'un message M (les messages qui répondent à M)
476 % ordrés du plus petit au plus grand.
477 % @spec enfants_id(integer()) -> [integer()]
478 enfants_id(M_id) ->
479 resultat_transaction(mnesia:transaction(fun() ->
480 case mnesia:index_read(reponse_minichat, M_id, #reponse_minichat.cible) of
481 Enfants when is_list(Enfants) ->
482 lists:sort(lists:map(
483 fun(#reponse_minichat{repondant = Repondant}) -> Repondant end,
484 Enfants
485 ));
486 _ -> []
487 end
488 end)).
489
490
491 % Est-ce que le message Id_mess est une réponse d'une message de Id_user ?
492 % On evite d'utiliser qlc (ce qui était fait avant) pour des raisons de performance.
493 est_une_reponse_a_user(Id_user, Id_mess) ->
494 resultat_transaction(mnesia:transaction(
495 fun() ->
496 case mnesia:read({reponse_minichat, Id_mess}) of
497 [] -> false;
498 Cibles ->
499 lists:any(
500 fun(#reponse_minichat{cible = Cible}) ->
501 case mnesia:read({minichat, Cible}) of
502 [#minichat{auteur_id = Id_user}] -> true;
503 _ -> false
504 end
505 end,
506 Cibles
507 )
508 end
509 end
510 )).
511
512
513 % Est-ce que Id_user à répondu au message Id_mess
514 % On evite d'utiliser qlc (ce qui était fait avant) pour des raisons de performance.
515 a_repondu_a_message(Id_user, Id_mess) ->
516 resultat_transaction(mnesia:transaction(
517 fun() ->
518 case mnesia:index_read(reponse_minichat, Id_mess, #reponse_minichat.cible) of
519 [] -> false;
520 Reponses ->
521 lists:any(
522 fun(#reponse_minichat{repondant = Reponse}) ->
523 case mnesia:read({minichat, Reponse}) of
524 [#minichat{auteur_id = Id_user}] -> true;
525 _ -> false
526 end
527 end,
528 Reponses
529 )
530 end
531 end
532 )).
533
534
535 % Est-ce que Id_user possède Id_mess ?
536 possede_message(Id_user, Id_mess) ->
537 case mnesia:transaction(
538 fun() ->
539 e(q([E#minichat.auteur_id || E <- mnesia:table(minichat), E#minichat.id =:= Id_mess]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
540 end
541 ) of
542 {atomic, [Id_user | []]} -> true;
543 _ -> false
544 end.
545
546
547 % renvoie la liste des ip bannies
548 % liste de {ip, temps_restant(en minutes), Users} ou Users est une liste de {pseudo, login}
549 % TODO : déterminer la complexité de cette fonction. (Il n'y a pas d'index sur #user.last_ip)
550 list_ban() ->
551 resultat_transaction(mnesia:transaction(
552 fun() ->
553 Now = erlang:timestamp(),
554 e(qlc:keysort(1, q([
555 {
556 IP#ip_table.ip,
557 euphorik_common:delta_date_minute(date_plus_minutes(IP#ip_table.ban, IP#ip_table.ban_duration), Now),
558 e(q([{Profile#profile.pseudo, U#user.login} || #user{profile = Profile} = U <- mnesia:table(user), U#user.last_ip =:= IP#ip_table.ip]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
559 } ||
560 IP <- mnesia:table(ip_table),
561 if IP#ip_table.ban =:= undefined -> false; true -> date_plus_minutes(IP#ip_table.ban, IP#ip_table.ban_duration) > Now end
562 ])), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
563 end
564 )).
565
566
567 % Bannie une ip pour un certain temps (en minute).
568 ban(IP, Duration) ->
569 mnesia:transaction(
570 fun() ->
571 case mnesia:wread({ip_table, IP}) of
572 [IP_tuple] ->
573 mnesia:write(IP_tuple#ip_table{ban = erlang:timestamp(), ban_duration = Duration});
574 _ ->
575 mnesia:write(#ip_table{ip = IP, ban = erlang:timestamp(), ban_duration = Duration})
576 end
577 end
578 ).
579
580
581 % Débanni une ip
582 deban(IP) ->
583 ban(IP, 0).
584
585
586 % Renvoie soit {true, Temps} où Temps est le temps en minutes pendant lequel le user est encore banni
587 % ou false.
588 est_banni(User_id) ->
589 resultat_transaction(mnesia:transaction(
590 fun() ->
591 case e(q([
592 {IP#ip_table.ban, IP#ip_table.ban_duration} ||
593 U <- mnesia:table(user),
594 U#user.id =:= User_id,
595 IP <- mnesia:table(ip_table),
596 IP#ip_table.ip =:= U#user.last_ip
597 ]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
598 [{Ban, Ban_duration}] ->
599 Echeance = date_plus_minutes(Ban, Ban_duration),
600 Now = erlang:timestamp(),
601 if Echeance < Now -> % l'échéance est passée
602 false;
603 true ->
604 {true, euphorik_common:delta_date_minute(Echeance, Now)}
605 end;
606 _ ->
607 false
608 end
609 end
610 )).
611
612
613 % Ban est une date tel que retourner par erlang:timestamp().
614 % Ban_duration est un temps en minutes.
615 % retourne une date.
616 date_plus_minutes(Ban, Ban_duration) ->
617 Duration_sec = Ban_duration * 60,
618 {MegaSec, Sec, MicroSec} = Ban,
619 {MegaSec + if Sec + Duration_sec >= 1000000 -> 1; true -> 0 end,(Sec + Duration_sec) rem 1000000, MicroSec}.
620
621
622 % Si deux enregistrements consequtifs de la même IP sont fait en moins d'une seconde alors
623 % ip_table.nb_try_register est incrémenté de 1 sinon il est décrémenté de 1 (jusqu'a 0).
624 % Si ip_table.nb_try_register vaut 5 alors l'ip ne peux plus s'enregistrer pour une heure.
625 can_register(IP) ->
626 resultat_transaction(mnesia:transaction(
627 fun() ->
628 case e(q([I || I <- mnesia:table(ip_table), I#ip_table.ip =:= IP]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
629 [] ->
630 mnesia:write(#ip_table{ip = IP, date_last_try_register = erlang:timestamp()}),
631 true;
632 [T] ->
633 Delta = euphorik_common:delta_date_ms(T#ip_table.date_last_try_register, erlang:timestamp()),
634 if T#ip_table.nb_try_register =:= ?NB_MAX_FLOOD_REGISTER, Delta < ?TEMPS_BAN_FLOOD_REGISTER ->
635 false;
636 true ->
637 mnesia:write(T#ip_table{
638 ip = IP,
639 date_last_try_register = erlang:timestamp(),
640 nb_try_register = T#ip_table.nb_try_register + if Delta < ?TEMPS_FLOOD_REGISTER -> 1; T#ip_table.nb_try_register > 0 -> -1; true -> 0 end
641 }),
642 true
643 end
644 end
645 end
646 )).
647
648
649 % Renvoie le résultat d'une transaction (en décomposant le tuple fournit)
650 resultat_transaction({_, T}) ->
651 T.
652
653
654 % Renvoie un nouvel id pour une table donnée
655 nouvel_id(Table) ->
656 mnesia:dirty_update_counter(counter, Table, 1).