ADD avancement sur la possibilité de réduire une conversation (pas fini)
[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 % @author G.Burri
23
24
25 -module(euphorik_bd).
26 -export([
27 % gestion :
28 create/0,
29 connect/0,
30 connect/1,
31 reset/0,
32
33 % users :
34 nouveau_user/2,
35 nouveau_user/3,
36 set_profile/11,
37 update_date_derniere_connexion/1,
38 update_ip/2,
39 update_pseudo_user/2,
40 print_users/0,
41 print_users/1,
42 print_user/1,
43 user_by_cookie/1,
44 user_by_id/1,
45 user_by_login/1,
46 user_by_login_password/2,
47 user_by_mess/1,
48 toggle_ek_master/1,
49 css_from_user_cookie/1,
50 is_ek_master_from_cookie/1,
51
52 % messages :e
53 nouveau_message/3,
54 nouveau_message_sys/1,
55 nouveau_message_sys/2,
56 messages/1,
57 messages/2,
58 messages/3,
59 message_by_id/1,
60 messages_by_ids/1,
61 message_existe/1,
62 reponses/0,
63 parents/1,
64 enfants/1,
65 parents_id/1,
66 enfants_id/1,
67 est_une_reponse_a_user/2,
68 a_repondu_a_message/2,
69 possede_message/2,
70
71 % ip :
72 list_ban/0,
73 ban/2,
74 deban/1,
75 est_banni/1,
76 can_register/1,
77
78 % trolls :
79 trolls/0,
80 trolls/1,
81 put_troll/2,
82 mod_troll/2,
83 del_troll/1,
84 troll_by_id/1,
85 current_troll/0,
86 elire_troll/0,
87 message_id_associe/1,
88
89 % utiles :
90 resultat_transaction/1
91 ]).
92 -import(qlc, [e/2, q/1, cursor/2]).
93 -include("../include/euphorik_bd.hrl").
94 -include("../include/euphorik_defines.hrl").
95 -include_lib("stdlib/include/qlc.hrl").
96
97
98 % Instructions pour créer une nouvelle base :
99 % $erl -sname yaws -mnesia dir '"/projets/euphorik/BD"'
100 % voir doc/installation.txt
101 % >l(euphorik_bd).
102 % >euphorik_bd:create().
103 create() ->
104 mnesia:stop(),
105 mnesia:delete_schema([node()]),
106 mnesia:create_schema([node()]), % nécessaire pour les tables sur disc
107 mnesia:start(),
108 create_tables(),
109 reset().
110
111 create_tables() ->
112 mnesia:create_table(counter, [
113 {attributes, record_info(fields, counter)},
114 {disc_copies, [node()]}
115 ]),
116 mnesia:create_table(proprietes, [
117 {attributes, record_info(fields, proprietes)},
118 {disc_copies, [node()]}
119 ]),
120 mnesia:create_table(minichat, [
121 {attributes, record_info(fields, minichat)},
122 {index, [auteur_id, troll_id]},
123 {disc_copies, [node()]}
124 ]),
125 mnesia:create_table(reponse_minichat, [
126 {type, bag},
127 {attributes, record_info(fields, reponse_minichat)},
128 {index, [cible]},
129 {disc_copies, [node()]}
130 ]),
131 mnesia:create_table(user, [
132 {attributes, record_info(fields, user)},
133 {index, [cookie, login]},
134 {disc_copies, [node()]}
135 ]),
136 mnesia:create_table(ip_table, [
137 {attributes, record_info(fields, ip_table)},
138 {disc_copies, [node()]}
139 ]),
140 mnesia:create_table(troll, [
141 {attributes, record_info(fields, troll)},
142 {index, [date_post]},
143 {disc_copies, [node()]}
144 ]).
145
146
147 % Connexion à la base de données de yaws sur overnux
148 connect() ->
149 connect(yaws@flynux).
150 connect(Node) ->
151 mnesia:start(),
152 mnesia:change_config(extra_db_nodes, [Node]).
153
154
155 % Efface tous les users, minichat_reponse et minichat.
156 reset() ->
157 mnesia:clear_table(counter),
158 mnesia:clear_table(proprietes),
159 mnesia:clear_table(user),
160 mnesia:clear_table(reponse_minichat),
161 mnesia:clear_table(minichat),
162 mnesia:clear_table(troll),
163 mnesia:clear_table(ip_table),
164 % crée l'utilisateur root
165 mnesia:transaction(fun() ->
166 mnesia:write(#proprietes{nom = version, valeur = ?VERSION_BD}),
167 User = #user{id = 0, pseudo = "Sys", login = "Sys", date_creation = now(), date_derniere_connexion = now(), ek_master = true},
168 mnesia:write(User),
169 User
170 end).
171
172
173 % Ajoute un nouveau user et le renvoie
174 nouveau_user(Pseudo, Cookie) ->
175 F = fun() ->
176 Id = nouvel_id(user),
177 User = #user{id = Id, cookie = Cookie, pseudo = Pseudo, date_creation = now(), date_derniere_connexion = now()},
178 mnesia:write(User),
179 User
180 end,
181 resultat_transaction(mnesia:transaction(F)).
182
183
184 % Ajoute un nouveau user et le renvoie
185 nouveau_user(Login, Password, Cookie) ->
186 F = fun() ->
187 Id = nouvel_id(user),
188 User = #user{id = Id, cookie = Cookie, pseudo = Login, login = Login, password = Password, date_creation = now(), date_derniere_connexion = now()},
189 mnesia:write(User),
190 User
191 end,
192 resultat_transaction(mnesia:transaction(F)).
193
194
195 % Mise à par Cookie les autres peuvent être undefined ce qui veut dire qu'ils ne seront pas modifié.
196 % Conversation est de type [{int(), bool()}] où l'entier est la racine, le booléen indique si la conversation est réduite ou non
197 % Ostentatious_master peut valoir invisible, light ou heavy
198 set_profile(Cookie, Login, Password, Pseudo, Email, Css, Nick_format, View_times, View_tooltips, Conversations, Ostentatious_master) ->
199 if Nick_format =:= nick; Nick_format =:= login; Nick_format =:= nick_login,
200 Ostentatious_master =:= invisible; Ostentatious_master =:= light; Ostentatious_master =:= heavy ->
201 resultat_transaction(mnesia:transaction(
202 fun() ->
203 case user_by_cookie(Cookie) of
204 {ok, User} ->
205 case user_by_login(Login) of
206 {ok, U} when Login =/= [], U#user.id =/= User#user.id ->
207 login_deja_pris;
208 _ ->
209 User_modifie = User#user{
210 % TODO : pourquoi ne pas tester avec la valeur "undefined" plutôt qu'avec "is_list" ?
211 % TODO : validation plus strict des données (pas de page négative dans les conv par exemple)
212 login = if is_list(Login) -> Login; true -> User#user.login end,
213 password = if is_list(Password) andalso Password =/= [] -> Password; true -> User#user.password end,
214 pseudo = if is_list(Pseudo) -> Pseudo; true -> User#user.pseudo end,
215 email = if is_list(Email) -> Email; true -> User#user.email end,
216 css = if is_list(Css) -> Css; true -> User#user.css end,
217 nick_format = Nick_format,
218 view_times = View_times,
219 view_tooltips = View_tooltips,
220 conversations = if is_list(Conversations) -> Conversations; true -> User#user.conversations end,
221 ostentatious_master = Ostentatious_master
222 },
223 mnesia:write(User_modifie),
224 ok
225 end;
226 _ -> erreur
227 end
228 end
229 ));
230 true ->
231 erreur
232 end.
233
234
235 % Met à jour la date de la dernière connexion d'un utilisateur à maintenant
236 update_date_derniere_connexion(User_id) ->
237 mnesia:transaction(
238 fun() ->
239 case mnesia:wread({user, User_id}) of
240 [User] ->
241 mnesia:write(User#user{date_derniere_connexion = now()});
242 _ ->
243 mnesia:abort("update_date_derniere_connexion: User inconnu")
244 end
245 end
246 ).
247
248
249 % Met à jour l'ip d'un user
250 update_ip(User_id, IP) ->
251 mnesia:transaction(
252 fun() ->
253 case mnesia:wread({user, User_id}) of
254 [User] ->
255 mnesia:write(User#user{last_ip = IP});
256 _ ->
257 mnesia:abort("update_ip: User inconnu")
258 end
259 end
260 ).
261
262
263 % Met à jour le pseudo du user
264 update_pseudo_user(UserId, Pseudo) ->
265 mnesia:transaction(
266 fun() ->
267 case mnesia:wread({user, UserId}) of
268 [User] when User#user.pseudo =/= Pseudo ->
269 mnesia:write(User#user{pseudo = Pseudo});
270 _ ->
271 mnesia:abort("update_pseudo_user: User inconnu ou pseudo deja à jour")
272 end
273 end
274 ).
275
276
277 % Affiche N user trié par leur date de dernière connexion.
278 % Opt est une liste d'option d'affichage :
279 % * ekmaster : n'affiche que les admins
280 print_users(N, Opt) ->
281 AfficheQueLesEkMaster = lists:any(fun(O) -> O =:= ekmaster end, Opt),
282 resultat_transaction(mnesia:transaction(fun() ->
283 C = cursor(
284 qlc:keysort(
285 #user.date_derniere_connexion,
286 if AfficheQueLesEkMaster ->
287 q([E || E <- mnesia:table(user), E#user.ek_master =:= true]);
288 true ->
289 q([E || E <- mnesia:table(user)])
290 end,
291 [{order, descending}]
292 ),
293 [{tmpdir, ?KEY_SORT_TEMP_DIR}]
294 ),
295 Users = qlc:next_answers(C, N),
296 lists:foreach(
297 fun(U) ->
298 print_user(U)
299 end,
300 Users
301 ),
302 qlc:delete_cursor(C)
303 end)).
304
305
306 % Affiche tous les users.
307 print_users(Opt) ->
308 print_users(all_remaining, Opt).
309
310 % Affiche tous les users.
311 print_users() ->
312 print_users(all_remaining, []).
313
314 print_user(User) when is_record(User, user) ->
315 #user{id = Id, pseudo = Pseudo, login = Login, ek_master = Ek_master, date_derniere_connexion = Date, last_ip = IP} = User,
316 {{Annee, Mois, Jour}, {Heure, Min, _}} = calendar:now_to_local_time(Date),
317 io:format(
318 % id pseudo (login) IP Jour Mois Année Heure Minute
319 "~4w : ~10.10..s(~10.10..s) ~s ~2w.~2.2.0w.~w - ~2wh~2.2.0w~n",
320 [
321 Id,
322 if Ek_master -> "*"; true -> "" end ++ Pseudo,
323 Login,
324 euphorik_common:serialize_ip(IP),
325 Jour, Mois, Annee, Heure, Min
326 ]
327 );
328 % Affichage d'un user en fonction de son login
329 print_user(Login) when is_list(Login) ->
330 case user_by_login(Login) of
331 {ok, User} ->
332 print_user(User);
333 _ ->
334 {erreur, "Login pas trouvé : " ++ Login}
335 end;
336 % Affichage d'un user en fonction de son id
337 print_user(Id) when is_integer(Id) ->
338 case user_by_id(Id) of
339 {ok, User} ->
340 print_user(User);
341 _ ->
342 {erreur, "Id pas trouvé : " ++ integer_to_list(Id)}
343 end.
344
345
346 % Est-ce qu'un utilisateur existe en fonction de son cookie ?
347 % Renvoie {ok, User} ou erreur
348 user_by_cookie(Cookie) ->
349 resultat_transaction(mnesia:transaction(
350 fun() ->
351 case e(q([E || E <- mnesia:table(user), E#user.cookie =:= Cookie]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
352 [User] -> {ok, User};
353 _ -> erreur
354 end
355 end
356 )).
357
358
359 user_by_id(ID) ->
360 resultat_transaction(mnesia:transaction(
361 fun() ->
362 case e(q([E || E <- mnesia:table(user), E#user.id =:= ID]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
363 [User] -> {ok, User};
364 _ -> erreur
365 end
366 end
367 )).
368
369
370 user_by_login(Login) ->
371 resultat_transaction(mnesia:transaction(
372 fun() ->
373 Users = e(q([E || E <- mnesia:table(user), E#user.login =:= Login]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]),
374 case Users of
375 [User] -> {ok, User};
376 _ -> erreur
377 end
378 end
379 )).
380
381
382 toggle_ek_master(User_id) ->
383 resultat_transaction(mnesia:transaction(
384 fun() ->
385 Users = e(q([E || E <- mnesia:table(user), E#user.id =:= User_id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]),
386 case Users of
387 [User] ->
388 mnesia:write(User#user{ek_master = not User#user.ek_master});
389 _ -> erreur
390 end
391 end
392 )).
393
394
395 % Renvoie une chaine représentant le cookie ou undefined si pas trouvé.
396 css_from_user_cookie(Cookie) ->
397 case user_by_cookie(Cookie) of
398 {ok, User} ->
399 User#user.css;
400 _ ->
401 undefined
402 end.
403
404
405 is_ek_master_from_cookie(Cookie) ->
406 case user_by_cookie(Cookie) of
407 {ok, #user{ek_master = true}} -> true;
408 _ -> false
409 end.
410
411
412 user_by_login_password(Login, Password) ->
413 resultat_transaction(mnesia:transaction(
414 fun() ->
415 case e(q([E || E <- mnesia:table(user), E#user.login =:= Login, E#user.password =:= Password]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
416 [User | _] -> {ok, User};
417 _ -> erreur
418 end
419 end
420 )).
421
422
423 % Renvoie {ok, User} où User est un #user possédant le message donné.
424 user_by_mess(Id) ->
425 resultat_transaction(mnesia:transaction(
426 fun() ->
427 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
428 [User | _] -> {ok, User};
429 _ -> erreur
430 end
431 end
432 )).
433
434
435 % Ajoute un message. Repond_A est une liste d'id auquel le message répond
436 % retourne soit l'id du message soit {erreur, <raison>}.
437 nouveau_message(Mess, Auteur_id, Repond_A_ids) ->
438 % regarde si les id 'Repond_A' existent
439 F = fun() ->
440 Repond_a = e(
441 q([M || M <- mnesia:table(minichat), lists:member(M#minichat.id, Repond_A_ids)]),
442 [{tmpdir, ?KEY_SORT_TEMP_DIR}]
443 ),
444 Racine_id = case Repond_a of
445 [] -> undefined;
446 [M | _ ] ->
447 Une_racine = M#minichat.racine_id,
448 % vérification que tout les messages de Repond_a possède la même racine (même conversation)
449 case lists:all(fun(R) -> R#minichat.racine_id =:= Une_racine end, Repond_a) of
450 true ->
451 Une_racine;
452 _ ->
453 {erreur, "Les messages ne font pas partie de la même conversation"}
454 end
455 end,
456 case Racine_id of
457 {erreur, E} -> {erreur, E};
458 _ ->
459 % est-ce que l'auteur existe ?
460 case e(q([E || E <- mnesia:table(user), E#user.id =:= Auteur_id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
461 [Auteur] ->
462 if length(Repond_a) =/= length(Repond_A_ids) ->
463 {erreur, "Un ou plusieurs messages introuvable"};
464 true ->
465 % comparaison entre la date du dernier poste et maintenant (gestion du flood)
466 Delta = delta_date_ms(Auteur#user.date_derniere_connexion, now()),
467 Nouvel_indice_flood = Auteur#user.indice_flood + if Delta =< ?DUREE_SPAM -> 2; true -> -1 end,
468 Auteur_maj = Auteur#user{
469 indice_flood = if Nouvel_indice_flood > ?INDICE_SPAM_MAX -> ?INDICE_SPAM_MAX; Nouvel_indice_flood < 0 -> 0; true -> Nouvel_indice_flood end,
470 date_derniere_connexion = now()
471 },
472 % est-ce que l'auteur à trop floodé ?
473 if Auteur#user.indice_flood =/= ?INDICE_SPAM_MAX, Auteur_maj#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM ->
474 mnesia:write(Auteur#user{indice_flood = Auteur_maj#user.indice_flood}),
475 nouveau_message_sys("''" ++ Auteur#user.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.");
476 Auteur#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM ->
477 {erreur, "Bloqué pour cause de flood"};
478 true ->
479 mnesia:write(Auteur_maj),
480 Id = nouvel_id(minichat),
481 inserer_reponses(Id, Repond_A_ids),
482 mnesia:write(#minichat{
483 id = Id,
484 auteur_id = Auteur#user.id,
485 date = now(),
486 pseudo = Auteur#user.pseudo,
487 contenu = Mess,
488 racine_id = if Racine_id =:= undefined -> Id; true -> Racine_id end
489 }),
490 Id
491 end
492 end;
493 _ ->
494 {erreur, "L'auteur du message est introuvable"}
495 end
496 end
497 end,
498 resultat_transaction(mnesia:transaction(F)).
499
500 % Définit Id_repondant comme étant la réponse à Ids. Ids est une liste d'id.
501 inserer_reponses(Id_repondant, [Id_mess | Reste]) ->
502 mnesia:write(#reponse_minichat{repondant = Id_repondant, cible = Id_mess}),
503 inserer_reponses(Id_repondant, Reste);
504 inserer_reponses(_, []) ->
505 ok.
506
507
508 % Permet de créer un message système.
509 % Renvoie l'id du message système
510 nouveau_message_sys(Mess) ->
511 nouveau_message_sys(Mess, undefined).
512
513
514 % Création d'un message système lié à un troll.
515 nouveau_message_sys(Mess, Troll_id) ->
516 {ok, Root} = user_by_id(0),
517 resultat_transaction(mnesia:transaction(
518 fun() ->
519 Id = nouvel_id(minichat),
520 mnesia:write(#minichat{id=Id, auteur_id=0, date=now(), pseudo=Root#user.pseudo, contenu=Mess, troll_id=Troll_id, racine_id=Id}),
521 Id
522 end
523 )).
524
525
526 % Renvoie N messages se trouvant sur la première page
527 messages(N) ->
528 messages(N, 1).
529
530
531 % Renvoie N messages se trouvant sur la page P
532 messages(N, P) ->
533 F = fun() ->
534 C = cursor(
535 qlc:keysort(
536 #minichat.id,
537 q([E#minichat{contenu = contenu_message(E)} || E <- mnesia:table(minichat)]),
538 [{order, descending}]
539 ),
540 [{tmpdir, ?KEY_SORT_TEMP_DIR}]
541 ),
542 if P > 1 -> qlc:next_answers(C, N * (P - 1));
543 true -> ok
544 end,
545 R = qlc:next_answers(C, N),
546 qlc:delete_cursor(C),
547 R
548 end,
549 lists:reverse(resultat_transaction(mnesia:transaction(F))).
550
551
552 % Renvoie les messages manquants pour la page P en sachant qu'il y a N message
553 % par page et que le dernier message que l'on possède est Id
554 messages(Id, N, P) ->
555 lists:filter(fun (M) -> M#minichat.id > Id end, messages(N, P)).
556
557
558 % Renvoie {ok, #minichat} (voir #minichat de euphorik_bd.hrl) à partir de son id.
559 message_by_id(Id) ->
560 resultat_transaction(mnesia:transaction(
561 fun() ->
562 case mnesia:read({minichat, Id}) of
563 [] -> erreur;
564 [M] ->
565 {ok, M#minichat{contenu = contenu_message(M)}}
566 end
567 end
568 )).
569
570
571 % Renvoie le contenu d'un message donnée, à utiliser à l'intérieur d'une transaction.
572 % TODO : Cette fonction pourrait être remplacé par un "outer-join", est-ce possible avec qlc ?
573 contenu_message(E) ->
574 case mnesia:read({troll, E#minichat.troll_id}) of
575 [] -> E#minichat.contenu;
576 [T] -> E#minichat.contenu ++ T#troll.content
577 end.
578
579
580 % Renvoie une liste de message (voir #minichat de euphorik_bd.hrl) à partir d'une liste d'id (Ids).
581 % TODO : optimisations ? serait-ce du O(n) ?
582 % Bon de toutes façons on s'en fout c'est pas utilisé :)
583 messages_by_ids(Ids) ->
584 resultat_transaction(mnesia:transaction(
585 fun() ->
586 e(qlc:keysort(
587 #minichat.id,
588 q([E || E <- mnesia:table(minichat), lists:any(fun(Id) -> Id =:= E#minichat.id end, Ids)]),
589 [{order, ascending}]
590 ),[{tmpdir, ?KEY_SORT_TEMP_DIR}])
591 end
592 )).
593
594
595 % Est-ce qu'un message existe ? Renvoie un boolean.
596 % TODO : ya pas plus simple ?
597 message_existe(Id) ->
598 resultat_transaction(mnesia:transaction(fun() ->
599 length(e(q([E#minichat.id || E <- mnesia:table(minichat), E#minichat.id =:= Id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])) =:= 1
600 end)).
601
602
603 % Renvoie les reponses (utilisé normalement uniquement pendant le debug).
604 reponses() ->
605 F = fun() ->
606 e(q([E || E <- mnesia:table(reponse_minichat)]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
607 end,
608 resultat_transaction(mnesia:transaction(F)).
609
610
611 % Renvoie les messages auquel M_id répond.
612 parents(M_id) ->
613 resultat_transaction(mnesia:transaction(
614 fun() ->
615 e(q(
616 [M || R <- mnesia:table(reponse_minichat),
617 M <- mnesia:table(minichat),
618 R#reponse_minichat.repondant =:= M_id,
619 M#minichat.id =:= R#reponse_minichat.cible]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
620 end
621 )).
622
623
624 % Renvoie les message qui repondent à M_id
625 enfants(M_id) ->
626 resultat_transaction(mnesia:transaction(
627 fun() ->
628 e(q(
629 [M || R <- mnesia:table(reponse_minichat),
630 M <- mnesia:table(minichat),
631 R#reponse_minichat.cible =:= M_id,
632 M#minichat.id =:= R#reponse_minichat.repondant]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
633 end
634 )).
635
636
637 % Renvoie les parents d'un message M (les messages auquels répond M)
638 % ordrés du plus petit au plus grand..
639 % @spec parents_id(integer()) -> [integer()]
640 parents_id(M) ->
641 resultat_transaction(mnesia:transaction(fun() ->
642 e(
643 qlc:sort(
644 q([E#reponse_minichat.cible || E <- mnesia:table(reponse_minichat), E#reponse_minichat.repondant =:= M]),
645 [{order, ascending}]
646 ),
647 [{tmpdir, ?KEY_SORT_TEMP_DIR}]
648 )
649 end)).
650
651
652 % Renvoie les id des enfants d'un message M (les messages qui répondent à M)
653 % ordrés du plus petit au plus grand.
654 % @spec enfants_id(integer()) -> [integer()]
655 enfants_id(M) ->
656 resultat_transaction(mnesia:transaction(fun() ->
657 e(
658 qlc:sort(
659 q([E#reponse_minichat.repondant || E <- mnesia:table(reponse_minichat), E#reponse_minichat.cible =:= M]),
660 [{order, ascending}]
661 ),
662 [{tmpdir, ?KEY_SORT_TEMP_DIR}]
663 )
664 end)).
665
666
667 % Est-ce que le message Id_mess est une réponse d'une message de Id_user ?
668 est_une_reponse_a_user(Id_user, Id_mess) ->
669 case mnesia:transaction(
670 fun() ->
671 e(q([
672 M#minichat.auteur_id || M <- mnesia:table(minichat), R <- mnesia:table(reponse_minichat),
673 M#minichat.auteur_id =:= Id_user, M#minichat.id =:= R#reponse_minichat.cible, R#reponse_minichat.repondant =:= Id_mess
674 ]), [{unique_all, true}, {tmpdir, ?KEY_SORT_TEMP_DIR}])
675 end
676 ) of
677 {atomic, [_]} -> true;
678 _ -> false
679 end.
680
681
682 % Est-ce que Id_user à répondu au message Id_mess
683 a_repondu_a_message(Id_user, Id_mess) ->
684 case mnesia:transaction(
685 fun() ->
686 e(q([
687 M#minichat.auteur_id || M <- mnesia:table(minichat), R <- mnesia:table(reponse_minichat),
688 R#reponse_minichat.cible =:= Id_mess, R#reponse_minichat.repondant =:= M#minichat.id, M#minichat.auteur_id =:= Id_user
689 ]), [{unique_all, true}, {tmpdir, ?KEY_SORT_TEMP_DIR}])
690 end
691 ) of
692 {atomic, [_]} -> true;
693 _ -> false
694 end.
695
696
697 % Est-ce que Id_user possède Id_mess ?
698 possede_message(Id_user, Id_mess) ->
699 case mnesia:transaction(
700 fun() ->
701 e(q([E#minichat.auteur_id || E <- mnesia:table(minichat), E#minichat.id =:= Id_mess]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
702 end
703 ) of
704 {atomic, [Id_user | []]} -> true;
705 _ -> false
706 end.
707
708
709 % renvoie la liste des ip bannies
710 % liste de {ip, temps_restant(en minutes), Users} ou Users est une liste de {pseudo, login}
711 % TODO : déterminer la complexité de cette fonction. (Il n'y a pas d'index sur #user.last_ip)
712 list_ban() ->
713 resultat_transaction(mnesia:transaction(
714 fun() ->
715 Now = now(),
716 e(qlc:keysort(1, q([
717 {
718 IP#ip_table.ip,
719 delta_date_minute(date_plus_minutes(IP#ip_table.ban, IP#ip_table.ban_duration), Now),
720 e(q([{U#user.pseudo, U#user.login} || U <- mnesia:table(user), U#user.last_ip =:= IP#ip_table.ip]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
721 } ||
722 IP <- mnesia:table(ip_table),
723 if IP#ip_table.ban =:= undefined -> false; true -> date_plus_minutes(IP#ip_table.ban, IP#ip_table.ban_duration) > Now end
724 ])), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
725 end
726 )).
727
728
729 % Bannie une ip pour un certain temps (en minute).
730 ban(IP, Duration) ->
731 mnesia:transaction(
732 fun() ->
733 case mnesia:wread({ip_table, IP}) of
734 [IP_tuple] ->
735 mnesia:write(IP_tuple#ip_table{ban = now(), ban_duration = Duration});
736 _ ->
737 mnesia:write(#ip_table{ip = IP, ban = now(), ban_duration = Duration})
738 end
739 end
740 ).
741
742
743 % Débanni une ip
744 deban(IP) ->
745 ban(IP, 0).
746
747
748 % Renvoie soit {true, Temps} où Temps est le temps en minutes pendant lequel le user est encore banni
749 % ou false.
750 est_banni(User_id) ->
751 resultat_transaction(mnesia:transaction(
752 fun() ->
753 case e(q([
754 {IP#ip_table.ban, IP#ip_table.ban_duration} ||
755 U <- mnesia:table(user),
756 U#user.id =:= User_id,
757 IP <- mnesia:table(ip_table),
758 IP#ip_table.ip =:= U#user.last_ip
759 ]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
760 [{Ban, Ban_duration}] ->
761 Echeance = date_plus_minutes(Ban, Ban_duration),
762 Now = now(),
763 if Echeance < Now -> % l'échéance est passée
764 false;
765 true ->
766 {true, delta_date_minute(Echeance, Now)}
767 end;
768 _ ->
769 false
770 end
771 end
772 )).
773
774
775 % Ban est une date tel que retourner par now().
776 % Ban_duration est un temps en minutes.
777 % retourne une date.
778 date_plus_minutes(Ban, Ban_duration) ->
779 Duration_sec = Ban_duration * 60,
780 {MegaSec, Sec, MicroSec} = Ban,
781 {MegaSec + if Sec + Duration_sec >= 1000000 -> 1; true -> 0 end,(Sec + Duration_sec) rem 1000000, MicroSec}.
782
783
784 % Si deux enregistrements consequtifs de la même IP sont fait en moins d'une seconde alors
785 % ip_table.nb_try_register est incrémenté de 1 sinon il est décrémenté de 1 (jusqu'a 0).
786 % Si ip_table.nb_try_register vaut 5 alors l'ip ne peux plus s'enregistrer pour une heure.
787 can_register(IP) ->
788 resultat_transaction(mnesia:transaction(
789 fun() ->
790 case e(q([I || I <- mnesia:table(ip_table), I#ip_table.ip =:= IP]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
791 [] ->
792 mnesia:write(#ip_table{ip = IP, date_last_try_register = now()}),
793 true;
794 [T] ->
795 Delta = delta_date_ms(T#ip_table.date_last_try_register, now()),
796 if T#ip_table.nb_try_register =:= ?NB_MAX_FLOOD_REGISTER, Delta < ?TEMPS_BAN_FLOOD_REGISTER ->
797 false;
798 true ->
799 mnesia:write(#ip_table{
800 ip = IP,
801 date_last_try_register = now(),
802 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
803 }),
804 true
805 end
806 end
807 end
808 )).
809
810
811 % Renvoie tous les trolls
812 trolls() ->
813 resultat_transaction(mnesia:transaction(
814 fun() ->
815 e(qlc:keysort(#troll.id, q([T || T <- mnesia:table(troll)])), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
816 end
817 )).
818
819
820 % Renvoie les trolls manquants posté après Last_id.
821 trolls(Last_id) ->
822 resultat_transaction(mnesia:transaction(
823 fun() ->
824 e(qlc:keysort(#troll.id, q([T || T <- mnesia:table(troll), T#troll.id > Last_id, T#troll.date_post =:= undefined])), [{tmpdir, ?KEY_SORT_TEMP_DIR}])
825 end
826 )).
827
828
829 % Crée un nouveau troll.
830 % Renvoie l'id du nouveau troll
831 % ou max_troll_reached_per_user si le nombre de troll posté par l'utilisateur max a été atteind
832 % ou max_troll_reached si le nombre de troll posté max a été atteind
833 % ou user_unknown
834 put_troll(User_id, Content) ->
835 resultat_transaction(mnesia:transaction(
836 fun() ->
837 % control le nombre de troll déjà posté
838 Nb_troll_poste_par_user = length(e(q(
839 [
840 E#troll.id || E <- mnesia:table(troll),
841 E#troll.id_user =:= User_id,
842 E#troll.date_post =:= undefined
843 ]
844 ), [{tmpdir, ?KEY_SORT_TEMP_DIR}])),
845 Nb_troll_poste_total = length(e(q(
846 [
847 E#troll.id || E <- mnesia:table(troll),
848 E#troll.date_post =:= undefined
849 ]
850 ), [{tmpdir, ?KEY_SORT_TEMP_DIR}])),
851 User = user_by_id(User_id),
852 case User of
853 {ok, _} ->
854 if Nb_troll_poste_par_user >= ?NB_MAX_TROLL_WAITING_BY_USER ->
855 max_troll_reached_per_user;
856 Nb_troll_poste_total >= ?NB_MAX_TROLL_WAITING ->
857 max_troll_reached;
858 true ->
859 Id = nouvel_id(minichat),
860 mnesia:write(#troll{id = Id, id_user = User_id, date_create = now(), content = Content}),
861 Id
862 end;
863 _ ->
864 user_unknown
865 end
866 end
867 )).
868
869
870 % renvoie ok | erreur
871 mod_troll(Troll_id, Content) ->
872 mnesia:transaction(
873 fun() ->
874 case mnesia:wread({troll, Troll_id}) of
875 [Troll = #troll{date_post = undefined}] ->
876 mnesia:write(Troll#troll{content = Content});
877 _ ->
878 mnesia:abort("mod_troll: Troll inconnu ou déjà posté")
879 end
880 end
881 ).
882
883
884 del_troll(Troll_id) ->
885 mnesia:transaction(
886 fun() ->
887 case mnesia:wread({troll, Troll_id}) of
888 [#troll{date_post = undefined}] ->
889 mnesia:delete({troll, Troll_id});
890 _ ->
891 mnesia:abort("mod_troll: Troll inconnu ou déjà posté")
892 end
893 end
894 ).
895
896
897 troll_by_id(Troll_id) ->
898 resultat_transaction(mnesia:transaction(
899 fun() ->
900 case e(q([T || T <- mnesia:table(troll), T#troll.id =:= Troll_id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
901 [T] -> {ok, T};
902 _ ->
903 erreur
904 end
905 end
906 )).
907
908
909 % Renvoie le troll actuel qui se trouve sur la page principale.
910 % Renvois aucun si pas de troll courant.
911 current_troll() ->
912 resultat_transaction(mnesia:transaction(
913 fun() ->
914 C = cursor(qlc:keysort(#troll.date_post, q([T || T <- mnesia:table(troll), T#troll.date_post =/= undefined]), [{order, descending}]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]),
915 R = case qlc:next_answers(C, 1) of
916 [T] -> T;
917 _ -> aucun
918 end,
919 qlc:delete_cursor(C),
920 R
921 end
922 )).
923
924
925 % Elit un troll au hasard parmis les trolls en attente (leur date_post =:= undefined)
926 % Un message est posté par 'Sys' et le troll elu est lié à ce message
927 % met à jour sa date de post.
928 % renvoie plus_de_trolls si il n'y a aucun troll en attente.
929 elire_troll() ->
930 {A1,A2,A3} = now(),
931 random:seed(A1, A2, A3),
932 mnesia:transaction(
933 fun() ->
934 case e(q([T || T <- mnesia:table(troll), T#troll.date_post =:= undefined]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
935 [] ->
936 plus_de_trolls;
937 Trolls ->
938 Troll = lists:nth(random:uniform(length(Trolls)), Trolls),
939 Troll2 = Troll#troll{date_post = now()},
940 mnesia:write(Troll2),
941 nouveau_message_sys("Troll de la semaine : ", Troll2#troll.id)
942 end
943 end
944 ).
945
946
947 % Renvoie l'id du message associé au troll dont l'id est donnée.
948 % Renvoie undefined si il n'y en a pas.
949 message_id_associe(Troll_id) ->
950 resultat_transaction(mnesia:transaction(
951 fun() ->
952 case e(q([M#minichat.id || M <- mnesia:table(minichat), M#minichat.troll_id =:= Troll_id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of
953 [Id] -> Id;
954 _ -> undefined
955 end
956 end
957 )).
958
959
960 % Renvoie le résultat d'une transaction (en décomposant le tuple fournit)
961 resultat_transaction({_, T}) ->
962 T.
963
964
965 % Retourne la difference entre deux timestamp (erlang:now()) en miliseconde
966 delta_date_ms(D1, D2) ->
967 1000000000 * abs(element(1, D1) - element(1, D2)) + 1000 * abs(element(2, D1) - element(2, D2)) + trunc(abs(element(3, D1) - element(3, D2)) / 1000).
968
969 % Retourne la différence entre deux timestamp (erlang:now) en minutes
970 delta_date_minute(D1, D2) ->
971 trunc(delta_date_ms(D1, D2) / 1000 / 60).
972
973
974 % Renvoie un nouvel id pour une table donnée
975 nouvel_id(Table) ->
976 mnesia:dirty_update_counter(counter, Table, 1).
977