X-Git-Url: http://git.euphorik.ch/?p=euphorik.git;a=blobdiff_plain;f=modules%2Ferl%2Feuphorik_bd.erl;h=4e470993240cd4649307dd840e27f0c7d8fbc3a2;hp=678505ba2422432321b787acfabe8d87fb63fee9;hb=8bede29111bde3481ed8b3d6637c241c8d241544;hpb=13bf850da2316cde3ad2ff8d5c26fb32928bf7b4 diff --git a/modules/erl/euphorik_bd.erl b/modules/erl/euphorik_bd.erl index 678505b..4e47099 100755 --- a/modules/erl/euphorik_bd.erl +++ b/modules/erl/euphorik_bd.erl @@ -1,8 +1,26 @@ -% coding: utf-8 +% coding: utf-8 +% Copyright 2008 Grégory Burri +% +% This file is part of Euphorik. +% +% Euphorik is free software: you can redistribute it and/or modify +% it under the terms of the GNU General Public License as published by +% the Free Software Foundation, either version 3 of the License, or +% (at your option) any later version. +% +% Euphorik is distributed in the hope that it will be useful, +% but WITHOUT ANY WARRANTY; without even the implied warranty of +% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +% GNU General Public License for more details. +% +% You should have received a copy of the GNU General Public License +% along with Euphorik. If not, see . +% % Ce module permet de gérer les données persistantes lié au site d'euphorik.ch. % Il permet d'ajouter des message, de demande les messages sur une page donnée, etc.. % Ce module utilise une base mnesia. -% @author G.Burri +% @author G.Burri + -module(euphorik_bd). -export([ @@ -11,24 +29,31 @@ connect/0, connect/1, reset/0, + update/0, + restore/1, % users : nouveau_user/2, nouveau_user/3, - set_profile/9, + set_profile/10, update_date_derniere_connexion/1, update_ip/2, update_pseudo_user/2, - users/0, + print_users/0, + print_users/1, + print_user/1, user_by_cookie/1, user_by_id/1, user_by_login/1, user_by_login_password/2, user_by_mess/1, toggle_ek_master/1, + css_from_user_cookie/1, % messages : nouveau_message/3, + nouveau_message_sys/1, + nouveau_message_sys/2, messages/1, messages/2, messages/3, @@ -42,17 +67,30 @@ possede_message/2, % ip : - ip_table/0, + list_ban/0, ban/2, + deban/1, + est_banni/1, + can_register/1, + + % trolls : + trolls/0, + trolls/1, + put_troll/2, + mod_troll/2, + del_troll/1, + troll_by_id/1, + current_troll/0, + elire_troll/0, + message_id_associe/1, % versions : update_version/1, % utiles : - can_register/1, resultat_transaction/1 -]). - +]). +-import(qlc, [e/2, q/1, cursor/2]). -include("../include/euphorik_bd.hrl"). -include("../include/euphorik_defines.hrl"). -include_lib("stdlib/include/qlc.hrl"). @@ -70,14 +108,19 @@ create() -> mnesia:start(), create_tables(), reset(). + create_tables() -> mnesia:create_table(counter, [ {attributes, record_info(fields, counter)}, {disc_copies, [node()]} ]), + mnesia:create_table(proprietes, [ + {attributes, record_info(fields, proprietes)}, + {disc_copies, [node()]} + ]), mnesia:create_table(minichat, [ {attributes, record_info(fields, minichat)}, - {index, [auteur_id]}, + {index, [auteur_id, troll_id]}, {disc_copies, [node()]} ]), mnesia:create_table(reponse_minichat, [ @@ -97,6 +140,7 @@ create_tables() -> ]), mnesia:create_table(troll, [ {attributes, record_info(fields, troll)}, + {index, [date_post]}, {disc_copies, [node()]} ]). @@ -111,18 +155,89 @@ connect(Node) -> % Efface tous les users, minichat_reponse et minichat. reset() -> - mnesia:clear_table(counter), + mnesia:clear_table(counter), + mnesia:clear_table(proprietes), mnesia:clear_table(user), mnesia:clear_table(reponse_minichat), - mnesia:clear_table(minichat), + mnesia:clear_table(minichat), + mnesia:clear_table(troll), + mnesia:clear_table(ip_table), % crée l'utilisateur root - mnesia:transaction(fun() -> - User = #user{id = 0, pseudo = "Sys", login = "Sys", date_creation = now(), date_derniere_connexion = now()}, + mnesia:transaction(fun() -> + mnesia:write(#proprietes{nom = version, valeur = ?VERSION_BD}), + User = #user{id = 0, pseudo = "Sys", login = "Sys", date_creation = now(), date_derniere_connexion = now(), ek_master = true}, mnesia:write(User), User end). +% Met à jour la bd, compare ?VERSION_BD avec la version dans la table 'proprietes' +% et exécute les patchs nécessaires. +update() -> + mnesia:transaction( + fun() -> + case mnesia:read({proprietes, version}) of + [#proprietes{nom = Version}] -> + update(Version); + _ -> + erreur + end + end + ). + + +% Mise à jour de la BD. +% attention : il est nécessaire de se trouver dans une transaction. +update(?VERSION_BD) -> fini; +update(Version) -> + case mnesia:backup(mnesia:system_info(directory) ++ "/backup" ++ integer_to_list(Version)) of + ok -> + case patch(Version) of + ok -> + mnesia:write(#proprietes{nom = version, valeur = Version + 1}), + update(Version + 1); + _ -> + erreur + end; + {error, Raison} -> {error, lists:flatten(io_lib:format("Erreur de création du backup de la version ~w : ~w", [Version, Raison]))} + end. + + +% Applique une modification de la BD pour passer d'une version à la suivante. +% crée un backup avant l'application du patch +% dans BD/backups nommé "backup" où et le numéro de la version. +% 1 -> 2 +patch(1) -> + % traitement des users + mnesia:transform_table( + user, + fun({user, Id, Cookie, Pseudo, Login, Password, Email, Date_creation, Date_derniere_connexion, Css, Nick_format, View_times, View_tooltips, Indice_flood, _Page_principale, Conversations, Ek_master, Last_ip}) -> + {user, Id, Cookie, Pseudo, Login, Password, Email, Date_creation, Date_derniere_connexion, Css, Nick_format, View_times, View_tooltips, reverse, Indice_flood, lists:map(fun({C, _}) -> C end, Conversations), Ek_master, Last_ip} + end, + record_info(fields, user), + user + ), + mnesia:transform_table( + minichat, + fun({minichat, Id, Auteur_id, Date, Pseudo, Contenu, Troll_id}) -> + {minichat, Id, Auteur_id, Date, Pseudo, Contenu, Troll_id, Id} + end, + record_info(fields, minichat), + minichat + ), + % met à jour les racines des messages + TODO ! -> mnesia:foldl(...) + ok. +% 2 -> 3 +%patch(2) -> + + +% Reviens à une version précédente de la base de données +% (les données insérées durant les versions plus récentes sont perdues) +restore(N) -> + mnesia:restore(mnesia:system_info(directory) ++ "/backup" ++ integer_to_list(N)). + + % Ajoute un nouveau user et le renvoie nouveau_user(Pseudo, Cookie) -> F = fun() -> @@ -146,26 +261,27 @@ nouveau_user(Login, Password, Cookie) -> % Mise à par Cookie les autres peuvent être undefined ce qui veut dire qu'ils ne seront pas modifié. -set_profile(Cookie, Login, Password, Pseudo, Email, Css, Nick_format, Page_principale, Conversations) -> +set_profile(Cookie, Login, Password, Pseudo, Email, Css, Nick_format, View_times, View_tooltips, Conversations) -> if Nick_format =:= nick; Nick_format =:= login; Nick_format =:= nick_login -> resultat_transaction(mnesia:transaction( fun() -> case user_by_cookie(Cookie) of - {ok, User} -> + {ok, User} -> case user_by_login(Login) of - {ok, U} when User#user.login =/= [], U#user.id =/= User#user.id -> + {ok, U} when Login =/= [], U#user.id =/= User#user.id -> login_deja_pris; - _ -> + _ -> User_modifie = User#user{ % TODO : pourquoi ne pas tester avec la valeur "undefined" plutôt qu'avec "is_list" ? % TODO : validation plus strict des données (pas de page négative dans les conv par exemple) login = if is_list(Login) -> Login; true -> User#user.login end, - password = if is_list(Password) -> Password; true -> User#user.password end, + password = if is_list(Password) andalso Password =/= [] -> Password; true -> User#user.password end, pseudo = if is_list(Pseudo) -> Pseudo; true -> User#user.pseudo end, email = if is_list(Email) -> Email; true -> User#user.email end, css = if is_list(Css) -> Css; true -> User#user.css end, nick_format = Nick_format, - page_principale = if is_integer(Page_principale), Page_principale > 0 -> Page_principale; true -> User#user.page_principale end, + view_times = View_times, + view_tooltips = View_tooltips, conversations = if is_list(Conversations) -> Conversations; true -> User#user.conversations end }, mnesia:write(User_modifie), @@ -221,12 +337,74 @@ update_pseudo_user(UserId, Pseudo) -> end ). - -% Renvoie tous les users. -users() -> + +% Affiche N user trié par leur date de dernière connexion. +% Opt est une liste d'option d'affichage : +% * ekmaster : n'affiche que les admins +print_users(N, Opt) -> + AfficheQueLesEkMaster = lists:any(fun(O) -> O =:= ekmaster end, Opt), resultat_transaction(mnesia:transaction(fun() -> - qlc:e(qlc:q([E || E <- mnesia:table(user)])) - end)). + C = cursor( + qlc:keysort( + #user.date_derniere_connexion, + if AfficheQueLesEkMaster -> + q([E || E <- mnesia:table(user), E#user.ek_master =:= true]); + true -> + q([E || E <- mnesia:table(user)]) + end, + [{order, descending}] + ), + [{tmpdir, ?KEY_SORT_TEMP_DIR}] + ), + Users = qlc:next_answers(C, N), + lists:foreach( + fun(U) -> + print_user(U) + end, + Users + ), + qlc:delete_cursor(C) + end)). + + +% Affiche tous les users. +print_users(Opt) -> + print_users(all_remaining, Opt). + +% Affiche tous les users. +print_users() -> + print_users(all_remaining, []). + +print_user(User) when is_record(User, user) -> + #user{id = Id, pseudo = Pseudo, login = Login, ek_master = Ek_master, date_derniere_connexion = Date, last_ip = IP} = User, + {{Annee, Mois, Jour}, {Heure, Min, _}} = calendar:now_to_local_time(Date), + io:format( + % id pseudo (login) IP Jour Mois Année Heure Minute + "~4w : ~10.10..s(~10.10..s) ~s ~2w.~2.2.0w.~w - ~2wh~2.2.0w~n", + [ + Id, + if Ek_master -> "*"; true -> "" end ++ Pseudo, + Login, + euphorik_common:serialize_ip(IP), + Jour, Mois, Annee, Heure, Min + ] + ); +% Affichage d'un user en fonction de son login +print_user(Login) when is_list(Login) -> + case user_by_login(Login) of + {ok, User} -> + print_user(User); + _ -> + {erreur, "Login pas trouvé : " ++ Login} + end; +% Affichage d'un user en fonction de son id +print_user(Id) when is_integer(Id) -> + case user_by_id(Id) of + {ok, User} -> + print_user(User); + _ -> + {erreur, "Id pas trouvé : " ++ integer_to_list(Id)} + end. % Est-ce qu'un utilisateur existe en fonction de son cookie ? @@ -234,8 +412,7 @@ users() -> user_by_cookie(Cookie) -> resultat_transaction(mnesia:transaction( fun() -> - Users = qlc:e(qlc:q([E || E <- mnesia:table(user), E#user.cookie =:= Cookie])), - case Users of + case e(q([E || E <- mnesia:table(user), E#user.cookie =:= Cookie]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of [User] -> {ok, User}; _ -> erreur end @@ -246,8 +423,7 @@ user_by_cookie(Cookie) -> user_by_id(ID) -> resultat_transaction(mnesia:transaction( fun() -> - Users = qlc:e(qlc:q([E || E <- mnesia:table(user), E#user.id =:= ID])), - case Users of + case e(q([E || E <- mnesia:table(user), E#user.id =:= ID]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of [User] -> {ok, User}; _ -> erreur end @@ -258,7 +434,7 @@ user_by_id(ID) -> user_by_login(Login) -> resultat_transaction(mnesia:transaction( fun() -> - Users = qlc:e(qlc:q([E || E <- mnesia:table(user), E#user.login =:= Login])), + Users = e(q([E || E <- mnesia:table(user), E#user.login =:= Login]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]), case Users of [User] -> {ok, User}; _ -> erreur @@ -270,7 +446,7 @@ user_by_login(Login) -> toggle_ek_master(User_id) -> resultat_transaction(mnesia:transaction( fun() -> - Users = qlc:e(qlc:q([E || E <- mnesia:table(user), E#user.id =:= User_id])), + Users = e(q([E || E <- mnesia:table(user), E#user.id =:= User_id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]), case Users of [User] -> mnesia:write(User#user{ek_master = not User#user.ek_master}); @@ -279,13 +455,22 @@ toggle_ek_master(User_id) -> end )). + +% Renvoie une chaine représentant le cookie ou undefined si pas trouvé. +css_from_user_cookie(Cookie) -> + case user_by_cookie(Cookie) of + {ok, User} -> + User#user.css; + _ -> + undefined + end. + user_by_login_password(Login, Password) -> resultat_transaction(mnesia:transaction( fun() -> - Users = qlc:e(qlc:q([E || E <- mnesia:table(user), E#user.login =:= Login, E#user.password =:= Password])), - case Users of - [User] -> {ok, User}; + case e(q([E || E <- mnesia:table(user), E#user.login =:= Login, E#user.password =:= Password]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of + [User | _] -> {ok, User}; _ -> erreur end end @@ -296,55 +481,49 @@ user_by_login_password(Login, Password) -> user_by_mess(Id) -> resultat_transaction(mnesia:transaction( fun() -> - case qlc:e(qlc:q([U || U <- mnesia:table(user), M <- mnesia:table(minichat), M#minichat.id =:= Id, M#minichat.auteur_id =:= U#user.id])) of - [User] -> {ok, User}; + 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 + [User | _] -> {ok, User}; _ -> erreur end end )). - -% Renvoie l'utilisateur root -root() -> - {ok, User} = user_by_id(0), - User. - % Ajoute un message. Repond_A est une liste d'id auquel le message répond -% retourne soit l'id du message soit erreur. +% retourne soit l'id du message soit {erreur, }. nouveau_message(Mess, Auteur_id, Repond_A) -> % regarde si les id 'Repond_A' existent F = fun() -> - Nb_id_trouve = length(qlc:e(qlc:q([E#minichat.id || E <- mnesia:table(minichat), lists:member(E#minichat.id, Repond_A)]))), + Nb_id_trouve = length(e(q([E#minichat.id || E <- mnesia:table(minichat), lists:member(E#minichat.id, Repond_A)]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])), % est-ce que l'auteur existe ? - Auteur = case qlc:e(qlc:q([E || E <- mnesia:table(user), E#user.id =:= Auteur_id])) of - [A] -> A; - _ -> throw("L'auteur du message est introuvable") - end, - if Nb_id_trouve =/= length(Repond_A) -> throw("Un ou plusieurs messages introuvable"); - true -> ok - end, - Id = nouvel_id(minichat), - % compare les dernière - Delta = delta_date_ms(Auteur#user.date_derniere_connexion, now()), - Nouvel_indice_flood = Auteur#user.indice_flood + if Delta =< ?DUREE_SPAM -> 2; true -> -1 end, - Auteur_maj = Auteur#user{ - indice_flood = if Nouvel_indice_flood > ?INDICE_SPAM_MAX -> ?INDICE_SPAM_MAX; Nouvel_indice_flood < 0 -> 0; true -> Nouvel_indice_flood end, - date_derniere_connexion = now() - }, - % est-ce que l'auteur à trop floodé ? - if Auteur#user.indice_flood =/= ?INDICE_SPAM_MAX, Auteur_maj#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM -> - Root = root(), - mnesia:write(Auteur#user{indice_flood = Auteur_maj#user.indice_flood}), - mnesia:write(#minichat{id=Id, auteur_id=Root#user.id, date=now(), pseudo=Root#user.pseudo, contenu=Auteur#user.pseudo ++ "(" ++ Auteur#user.login ++ ") est bloqué pour " ++ integer_to_list(trunc(?DUREE_BLOCAGE_SPAM / 1000)) ++ " secondes pour cause de flood.."}), - Id; - Auteur#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM -> - erreur; - true -> - mnesia:write(Auteur_maj), - inserer_reponses(Id, Repond_A), - mnesia:write(#minichat{id=Id, auteur_id=Auteur#user.id, date=now(), pseudo=Auteur#user.pseudo, contenu=Mess}), - Id + case e(q([E || E <- mnesia:table(user), E#user.id =:= Auteur_id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of + [Auteur] -> + if Nb_id_trouve =/= length(Repond_A) -> + {erreur, "Un ou plusieurs messages introuvable"}; + true -> + % comparaison entre la date du dernier poste et maintenant (gestion du flood) + Delta = delta_date_ms(Auteur#user.date_derniere_connexion, now()), + Nouvel_indice_flood = Auteur#user.indice_flood + if Delta =< ?DUREE_SPAM -> 2; true -> -1 end, + Auteur_maj = Auteur#user{ + indice_flood = if Nouvel_indice_flood > ?INDICE_SPAM_MAX -> ?INDICE_SPAM_MAX; Nouvel_indice_flood < 0 -> 0; true -> Nouvel_indice_flood end, + date_derniere_connexion = now() + }, + % est-ce que l'auteur à trop floodé ? + if Auteur#user.indice_flood =/= ?INDICE_SPAM_MAX, Auteur_maj#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM -> + mnesia:write(Auteur#user{indice_flood = Auteur_maj#user.indice_flood}), + 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."); + Auteur#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM -> + {erreur, "Bloqué pour cause de flood"}; + true -> + mnesia:write(Auteur_maj), + Id = nouvel_id(minichat), + inserer_reponses(Id, Repond_A), + mnesia:write(#minichat{id=Id, auteur_id=Auteur#user.id, date=now(), pseudo=Auteur#user.pseudo, contenu=Mess}), + Id + end + end; + _ -> + {erreur, "L'auteur du message est introuvable"} end end, resultat_transaction(mnesia:transaction(F)). @@ -356,6 +535,24 @@ inserer_reponses(Id_repondant, [Id_mess | Reste]) -> inserer_reponses(_, []) -> ok. + +% Permet de créer un message système. +% Renvoie l'id du message système +nouveau_message_sys(Mess) -> + nouveau_message_sys(Mess, undefined). + + +% Création d'un message système lié à un troll. +nouveau_message_sys(Mess, Troll_id) -> + {ok, Root} = user_by_id(0), + resultat_transaction(mnesia:transaction( + fun() -> + Id = nouvel_id(minichat), + mnesia:write(#minichat{id=Id, auteur_id=0, date=now(), pseudo=Root#user.pseudo, contenu=Mess, troll_id=Troll_id}), + Id + end + )). + % Renvoie N messages se trouvant sur la première page messages(N) -> @@ -363,17 +560,24 @@ messages(N) -> % Renvoie N messages se trouvant sur la page P -messages(N, P) -> +messages(N, P) -> F = fun() -> - C = qlc:cursor(qlc:q([E || E <- qlc:keysort(2, mnesia:table(minichat), [{order, descending}])])), + C = cursor( + qlc:keysort( + #minichat.id, + q([E#minichat{contenu = contenu_message(E)} || E <- mnesia:table(minichat)]), + [{order, descending}] + ), + [{tmpdir, ?KEY_SORT_TEMP_DIR}] + ), if P > 1 -> qlc:next_answers(C, N * (P - 1)); true -> ok end, R = qlc:next_answers(C, N), qlc:delete_cursor(C), - lists:reverse(R) + R end, - resultat_transaction(mnesia:transaction(F)). + lists:reverse(resultat_transaction(mnesia:transaction(F))). % Renvoie les messages manquants pour la page P en sachant qu'il y a N message @@ -384,23 +588,37 @@ messages(Id, N, P) -> % Renvoie {ok, #minichat} (voir #minichat de euphorik_bd.hrl) à partir de son id. message_by_id(Id) -> - case resultat_transaction(mnesia:transaction( + resultat_transaction(mnesia:transaction( fun() -> - qlc:e(qlc:q([E || E <- qlc:keysort(2, mnesia:table(minichat), [{order, ascending}]), Id =:= E#minichat.id])) + case mnesia:read({minichat, Id}) of + [] -> erreur; + [M] -> + {ok, M#minichat{contenu = contenu_message(M)}} + end end - )) of - [M] -> {ok, M}; - _ -> erreur + )). + + +% Renvoie le contenu d'un message donnée, à utiliser à l'intérieur d'une transaction. +% TODO : Cette fonction pourrait être remplacé par un "outer-join", est-ce possible avec qlc ? +contenu_message(E) -> + case mnesia:read({troll, E#minichat.troll_id}) of + [] -> E#minichat.contenu; + [T] -> E#minichat.contenu ++ T#troll.content end. % Renvoie une liste de message (voir #minichat de euphorik_bd.hrl) à partir d'une liste d'id (Ids). +% TODO : optimisations ? serait-ce du O(n) ? +% Bon de toutes façons on s'en fout c'est pas utilisé :) messages_by_ids(Ids) -> resultat_transaction(mnesia:transaction( fun() -> - % TODO : optimisations ? serait-ce du O(n) ? - Query = qlc:q([E || E <- qlc:keysort(2, mnesia:table(minichat), [{order, ascending}]), lists:any(fun(Id) -> Id =:= E#minichat.id end, Ids)]), - qlc:e(Query) + e(qlc:keysort( + #minichat.id, + q([E || E <- mnesia:table(minichat), lists:any(fun(Id) -> Id =:= E#minichat.id end, Ids)]), + [{order, ascending}] + ),[{tmpdir, ?KEY_SORT_TEMP_DIR}]) end )). @@ -409,14 +627,14 @@ messages_by_ids(Ids) -> % TODO : ya pas plus simple ? message_existe(Id) -> resultat_transaction(mnesia:transaction(fun() -> - length(qlc:e(qlc:q([E#minichat.id || E <- mnesia:table(minichat), E#minichat.id =:= Id]))) =:= 1 + length(e(q([E#minichat.id || E <- mnesia:table(minichat), E#minichat.id =:= Id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}])) =:= 1 end)). % Renvoie les reponses (utilisé normalement uniquement pendant le debug). reponses() -> F = fun() -> - qlc:e(qlc:q([E || E <- mnesia:table(reponse_minichat)])) + e(q([E || E <- mnesia:table(reponse_minichat)]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) end, resultat_transaction(mnesia:transaction(F)). @@ -425,11 +643,24 @@ reponses() -> repond_a(M_id) -> resultat_transaction(mnesia:transaction( fun() -> - qlc:e(qlc:q( + e(q( [M || E <- mnesia:table(reponse_minichat), M <- mnesia:table(minichat), E#reponse_minichat.repondant =:= M_id, - M#minichat.id =:= E#reponse_minichat.cible])) + M#minichat.id =:= E#reponse_minichat.cible]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) + end + )). + + +% Renvoie les message qui repondent à M_id +reponses(M_id) -> + resultat_transaction(mnesia:transaction( + fun() -> + e(q( + [M || E <- mnesia:table(reponse_minichat), + M <- mnesia:table(minichat), + E#reponse_minichat.cible =:= M_id, + M#minichat.id =:= E#reponse_minichat.repondant]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) end )). @@ -438,10 +669,10 @@ repond_a(M_id) -> est_une_reponse_a_user(Id_user, Id_mess) -> case mnesia:transaction( fun() -> - qlc:e(qlc:q([ + e(q([ M#minichat.auteur_id || M <- mnesia:table(minichat), R <- mnesia:table(reponse_minichat), M#minichat.auteur_id =:= Id_user, M#minichat.id =:= R#reponse_minichat.cible, R#reponse_minichat.repondant =:= Id_mess - ]), [{unique_all, true}]) + ]), [{unique_all, true}, {tmpdir, ?KEY_SORT_TEMP_DIR}]) end ) of {atomic, [_]} -> true; @@ -453,10 +684,10 @@ est_une_reponse_a_user(Id_user, Id_mess) -> a_repondu_a_message(Id_user, Id_mess) -> case mnesia:transaction( fun() -> - qlc:e(qlc:q([ + e(q([ M#minichat.auteur_id || M <- mnesia:table(minichat), R <- mnesia:table(reponse_minichat), R#reponse_minichat.cible =:= Id_mess, R#reponse_minichat.repondant =:= M#minichat.id, M#minichat.auteur_id =:= Id_user - ]), [{unique_all, true}]) + ]), [{unique_all, true}, {tmpdir, ?KEY_SORT_TEMP_DIR}]) end ) of {atomic, [_]} -> true; @@ -468,18 +699,30 @@ a_repondu_a_message(Id_user, Id_mess) -> possede_message(Id_user, Id_mess) -> case mnesia:transaction( fun() -> - qlc:e(qlc:q([E#minichat.auteur_id || E <- mnesia:table(minichat), E#minichat.id =:= Id_mess])) + e(q([E#minichat.auteur_id || E <- mnesia:table(minichat), E#minichat.id =:= Id_mess]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) end ) of {atomic, [Id_user | []]} -> true; _ -> false end. - -ip_table() -> + +% renvoie la liste des ip bannies +% liste de {ip, temps_restant(en minutes), Users} ou Users est une liste de {pseudo, login} +% TODO : déterminer la complexité de cette fonction. (Il n'y a pas d'index sur #user.last_ip) +list_ban() -> resultat_transaction(mnesia:transaction( fun() -> - qlc:e(qlc:q([IP || IP <- mnesia:table(ip_table)])) + Now = now(), + e(qlc:keysort(1, q([ + { + IP#ip_table.ip, + delta_date_minute(date_plus_minutes(IP#ip_table.ban, IP#ip_table.ban_duration), Now), + 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}]) + } || + IP <- mnesia:table(ip_table), + if IP#ip_table.ban =:= undefined -> false; true -> date_plus_minutes(IP#ip_table.ban, IP#ip_table.ban_duration) > Now end + ])), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) end )). @@ -497,23 +740,55 @@ ban(IP, Duration) -> end ). + +% Débanni une ip +deban(IP) -> + ban(IP, 0). + + +% Renvoie soit {true, Temps} où Temps est le temps en minutes pendant lequel le user est encore banni +% ou false. +est_banni(User_id) -> + resultat_transaction(mnesia:transaction( + fun() -> + case e(q([ + {IP#ip_table.ban, IP#ip_table.ban_duration} || + U <- mnesia:table(user), + U#user.id =:= User_id, + IP <- mnesia:table(ip_table), + IP#ip_table.ip =:= U#user.last_ip + ]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of + [{Ban, Ban_duration}] -> + Echeance = date_plus_minutes(Ban, Ban_duration), + Now = now(), + if Echeance < Now -> % l'échéance est passée + false; + true -> + {true, delta_date_minute(Echeance, Now)} + end; + _ -> + false + end + end + )). -update_version(1) -> - mnesia:transform_table( - ip_table, - fun() -> null end, - record_info(fields, ip_table), - ip_table - ). +% Ban est une date tel que retourner par now(). +% Ban_duration est un temps en minutes. +% retourne une date. +date_plus_minutes(Ban, Ban_duration) -> + Duration_sec = Ban_duration * 60, + {MegaSec, Sec, MicroSec} = Ban, + {MegaSec + if Sec + Duration_sec >= 1000000 -> 1; true -> 0 end,(Sec + Duration_sec) rem 1000000, MicroSec}. + % Si deux enregistrements consequtifs de la même IP sont fait en moins d'une seconde alors % ip_table.nb_try_register est incrémenté de 1 sinon il est décrémenté de 1 (jusqu'a 0). -% Si ip_table.nb_try_register vaut 5 alors l'ip ne peux plus s'enregistrer pour une heure. +% Si ip_table.nb_try_register vaut 5 alors l'ip ne peux plus s'enregistrer pour une heure. can_register(IP) -> resultat_transaction(mnesia:transaction( fun() -> - case qlc:e(qlc:q([I || I <- mnesia:table(ip_table), I#ip_table.ip =:= IP])) of + case e(q([I || I <- mnesia:table(ip_table), I#ip_table.ip =:= IP]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of [] -> mnesia:write(#ip_table{ip = IP, date_last_try_register = now()}), true; @@ -533,6 +808,164 @@ can_register(IP) -> end )). + +% Renvoie tous les trolls +trolls() -> + resultat_transaction(mnesia:transaction( + fun() -> + e(qlc:keysort(#troll.id, q([T || T <- mnesia:table(troll)])), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) + end + )). + + +% Renvoie les trolls manquants posté après Last_id. +trolls(Last_id) -> + resultat_transaction(mnesia:transaction( + fun() -> + 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}]) + end + )). + + + % Crée un nouveau troll. + % Renvoie l'id du nouveau troll + % ou max_troll_reached_per_user si le nombre de troll posté par l'utilisateur max a été atteind + % ou max_troll_reached si le nombre de troll posté max a été atteind + % ou user_unknown +put_troll(User_id, Content) -> + resultat_transaction(mnesia:transaction( + fun() -> + % control le nombre de troll déjà posté + Nb_troll_poste_par_user = length(e(q( + [ + E#troll.id || E <- mnesia:table(troll), + E#troll.id_user =:= User_id, + E#troll.date_post =:= undefined + ] + ), [{tmpdir, ?KEY_SORT_TEMP_DIR}])), + Nb_troll_poste_total = length(e(q( + [ + E#troll.id || E <- mnesia:table(troll), + E#troll.date_post =:= undefined + ] + ), [{tmpdir, ?KEY_SORT_TEMP_DIR}])), + User = user_by_id(User_id), + case User of + {ok, _} -> + if Nb_troll_poste_par_user >= ?NB_MAX_TROLL_WAITING_BY_USER -> + max_troll_reached_per_user; + Nb_troll_poste_total >= ?NB_MAX_TROLL_WAITING -> + max_troll_reached; + true -> + Id = nouvel_id(minichat), + mnesia:write(#troll{id = Id, id_user = User_id, date_create = now(), content = Content}), + Id + end; + _ -> + user_unknown + end + end + )). + + +% renvoie ok | erreur +mod_troll(Troll_id, Content) -> + mnesia:transaction( + fun() -> + case mnesia:wread({troll, Troll_id}) of + [Troll = #troll{date_post = undefined}] -> + mnesia:write(Troll#troll{content = Content}); + _ -> + mnesia:abort("mod_troll: Troll inconnu ou déjà posté") + end + end + ). + + +del_troll(Troll_id) -> + mnesia:transaction( + fun() -> + case mnesia:wread({troll, Troll_id}) of + [#troll{date_post = undefined}] -> + mnesia:delete({troll, Troll_id}); + _ -> + mnesia:abort("mod_troll: Troll inconnu ou déjà posté") + end + end + ). + + +troll_by_id(Troll_id) -> + resultat_transaction(mnesia:transaction( + fun() -> + case e(q([T || T <- mnesia:table(troll), T#troll.id =:= Troll_id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of + [T] -> {ok, T}; + _ -> + erreur + end + end + )). + + +% Renvoie le troll actuel qui se trouve sur la page principale. +% Renvois aucun si pas de troll courant. +current_troll() -> + resultat_transaction(mnesia:transaction( + fun() -> + 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}]), + R = case qlc:next_answers(C, 1) of + [T] -> T; + _ -> aucun + end, + qlc:delete_cursor(C), + R + end + )). + + +% Elit un troll au hasard parmis les trolls en attente (leur date_post =:= undefined) +% Un message est posté par 'Sys' et le troll elu est lié à ce message +% met à jour sa date de post. +% renvoie plus_de_trolls si il n'y a aucun troll en attente. +elire_troll() -> + {A1,A2,A3} = now(), + random:seed(A1, A2, A3), + mnesia:transaction( + fun() -> + case e(q([T || T <- mnesia:table(troll), T#troll.date_post =:= undefined]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of + [] -> + plus_de_trolls; + Trolls -> + Troll = lists:nth(random:uniform(length(Trolls)), Trolls), + Troll2 = Troll#troll{date_post = now()}, + mnesia:write(Troll2), + nouveau_message_sys("Troll de la semaine : ", Troll2#troll.id) + end + end + ). + + +% Renvoie l'id du message associé au troll dont l'id est donnée. +% Renvoie undefined si il n'y en a pas. +message_id_associe(Troll_id) -> + resultat_transaction(mnesia:transaction( + fun() -> + case e(q([M#minichat.id || M <- mnesia:table(minichat), M#minichat.troll_id =:= Troll_id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of + [Id] -> Id; + _ -> undefined + end + end + )). + + +update_version(1) -> + mnesia:transform_table( + ip_table, + fun() -> null end, + record_info(fields, ip_table), + ip_table + ). + % Renvoie le résultat d'une transaction (en décomposant le tuple fournit) resultat_transaction({_, T}) -> @@ -543,6 +976,10 @@ resultat_transaction({_, T}) -> delta_date_ms(D1, D2) -> 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). +% Retourne la différence entre deux timestamp (erlang:now) en minutes +delta_date_minute(D1, D2) -> + trunc(delta_date_ms(D1, D2) / 1000 / 60). + % Bizarre, cette fonction n'existe pas dans la stdlib. % Pas utilisé mais bon ca me fait de la peine de l'enlever.