X-Git-Url: http://git.euphorik.ch/?p=euphorik.git;a=blobdiff_plain;f=modules%2Ferl%2Feuphorik_bd.erl;h=89c983612fb09f9045597a118419f54767eb382f;hp=be43e59211d19c80ed7820732d4c0a67d07f7d85;hb=08193845f5b45402b85977911dbd0693a4d183de;hpb=cd30bb86848bd5b52c46b8a0ff40cea6398de60e diff --git a/modules/erl/euphorik_bd.erl b/modules/erl/euphorik_bd.erl index be43e59..89c9836 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,11 +29,12 @@ connect/0, connect/1, reset/0, + update/0, % users : nouveau_user/2, nouveau_user/3, - set_profile/9, + set_profile/11, update_date_derniere_connexion/1, update_ip/2, update_pseudo_user/2, @@ -28,14 +47,17 @@ 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, message_by_id/1, + message_by_id_sans_transaction/1, messages_by_ids/1, message_existe/1, reponses/0, @@ -54,13 +76,13 @@ % trolls : trolls/0, trolls/1, - trolls_attente/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, @@ -86,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, [ @@ -128,20 +155,53 @@ 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(troll), mnesia:clear_table(ip_table), % crée l'utilisateur root - mnesia:transaction(fun() -> + 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) -> + patch(Version), + update(Version + 1). + + +% Applique une modification de la BD pour passer d'une version à la suivante. +% 1 -> 2 +patch(1) -> + ok. +% 2 -> 3 +%patch(2) -> + + % Ajoute un nouveau user et le renvoie nouveau_user(Pseudo, Cookie) -> F = fun() -> @@ -165,12 +225,12 @@ 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, Page_principale, 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 Login =/= [], U#user.id =/= User#user.id -> login_deja_pris; @@ -184,6 +244,8 @@ set_profile(Cookie, Login, Password, Pseudo, Email, Css, Nick_format, Page_princ 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, + view_times = View_times, + view_tooltips = View_tooltips, page_principale = if is_integer(Page_principale), Page_principale > 0 -> Page_principale; true -> User#user.page_principale end, conversations = if is_list(Conversations) -> Conversations; true -> User#user.conversations end }, @@ -245,7 +307,13 @@ update_pseudo_user(UserId, Pseudo) -> % Attention : pas d'index sur ce champs (la date) print_users(N) -> resultat_transaction(mnesia:transaction(fun() -> - C = cursor(q([E || E <- qlc:keysort(9, mnesia:table(user), [{order, descending}])])), + C = cursor( + qlc:keysort( + 9, + q([E || E <- mnesia:table(user)]), + [{order, descending}] + ) + ), Users = qlc:next_answers(C, N), lists:foreach( fun(U) -> @@ -340,7 +408,19 @@ 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() -> @@ -388,7 +468,7 @@ nouveau_message(Mess, Auteur_id, Repond_A) -> % 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."); + 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 -> @@ -413,13 +493,19 @@ inserer_reponses(_, []) -> % 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), - mnesia:transaction( + 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}) + 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 @@ -428,9 +514,15 @@ messages(N) -> % Renvoie N messages se trouvant sur la page P -messages(N, P) -> +messages(N, P) -> F = fun() -> - C = cursor(q([E || E <- qlc:keysort(2, mnesia:table(minichat), [{order, descending}])])), + C = cursor( + qlc:keysort( + 2, + q([E#minichat{contenu = contenu_message(E)} || E <- mnesia:table(minichat)]), + [{order, descending}] + ) + ), if P > 1 -> qlc:next_answers(C, N * (P - 1)); true -> ok end, @@ -449,13 +541,25 @@ 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() -> - e(q([E || E <- qlc:keysort(2, mnesia:table(minichat), [{order, ascending}]), Id =:= E#minichat.id])) + message_by_id_sans_transaction(Id) end - )) of - [M] -> {ok, M}; - _ -> erreur + )). +message_by_id_sans_transaction(Id) -> + case mnesia:read({minichat, Id}) of + [] -> erreur; + [M] -> + {ok, M#minichat{contenu = contenu_message(M)}} + end. + + +% 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. @@ -464,8 +568,11 @@ messages_by_ids(Ids) -> resultat_transaction(mnesia:transaction( fun() -> % TODO : optimisations ? serait-ce du O(n) ? - Query = q([E || E <- qlc:keysort(2, mnesia:table(minichat), [{order, ascending}]), lists:any(fun(Id) -> Id =:= E#minichat.id end, Ids)]), - e(Query) + e(qlc:keysort( + 2, + q([E || E <- mnesia:table(minichat), lists:any(fun(Id) -> Id =:= E#minichat.id end, Ids)]), + [{order, ascending}] + )) end )). @@ -548,15 +655,15 @@ list_ban() -> resultat_transaction(mnesia:transaction( fun() -> Now = now(), - e(q([ + 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])) } || - IP <- qlc:keysort(2, mnesia:table(ip_table)), + 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 - ])) + ]))) end )). @@ -647,7 +754,7 @@ can_register(IP) -> trolls() -> resultat_transaction(mnesia:transaction( fun() -> - e(q([T || T <- qlc:keysort(2, mnesia:table(troll))])) + e(qlc:keysort(2, q([T || T <- mnesia:table(troll)]))) end )). @@ -655,59 +762,13 @@ trolls() -> % Renvoie les trolls manquants posté après Last_id. trolls(Last_id) -> resultat_transaction(mnesia:transaction( - e(q([T || T <- qlc:keysort(2, mnesia:table(troll)), T#troll.id > Last_id, T#troll.date_post =:= undefined])) + fun() -> + e(qlc:keysort(2, q([T || T <- mnesia:table(troll), T#troll.id > Last_id, T#troll.date_post =:= undefined]))) + end )). - -% Renvoie les trolls manquants posté après Last_id. -% Si pas de trolls alors attend un événement tel qu'un ajout, une modification ou une suppression. -% renvoie : -% {mod, Troll} -% ou {add, [Trolls]} -% ou {del, Troll_id} -% ou timeout -trolls_attente(Last_id) -> - case mnesia:subscribe({table, troll, detailed}) of - {error, E} = E -> - E; - _ -> - R = case resultat_transaction(mnesia:transaction( - fun() -> - e(q([T || T <- qlc:keysort(2, mnesia:table(troll)), T#troll.id > Last_id, T#troll.date_post =:= undefined])) - end - )) of - [] -> % pas de trolls - attend_evenement_troll(); - Trolls -> - {add, Trolls} - end, - mnesia:unsubscribe({table, troll, detailed}), - R - end. - -attend_evenement_troll() -> - % s'il n'y a pas de trolls que l'utilisateur n'a pas connaissance alors on attend un événement - receive - % cas où un troll est choisit comme courant - {mnesia_table_event, {write, troll, Troll, [Old_troll | _], _}} when Old_troll#troll.date_post =:= undefined, Troll#troll.date_post =/= undefined -> - {del, Troll#troll.id}; - {mnesia_table_event, {write, troll, Troll, [_Old_troll | _], _}} -> - {mod, Troll}; - {mnesia_table_event, {write, troll, Troll, [], _}} -> - {add, [Troll]}; - {mnesia_table_event, {delete, troll, {troll, Id}, _, _}} -> - {del, Id}; - {tcp_closed, _} -> - exit(normal); - _ -> - attend_evenement_troll() - % 60 minutes de timeout (on ne sais jamais) - % Après 60 minutes de connexion, le client doit donc reétablir une connexion - after 1000 * 60 * 60 -> - timeout - 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 @@ -793,7 +854,7 @@ current_troll() -> resultat_transaction(mnesia:transaction( fun() -> % TODO : ya pas moyen de désigner le champs plutot qu'avec un nombre pour le tri ? - C = cursor(q([T || T <- qlc:keysort(5, mnesia:table(troll), [{order, descending}]), T#troll.date_post =/= undefined])), + C = cursor(qlc:keysort(5, q([T || T <- mnesia:table(troll), T#troll.date_post =/= undefined]), [{order, descending}])), R = case qlc:next_answers(C, 1) of [T] -> T; _ -> aucun @@ -805,6 +866,7 @@ current_troll() -> % 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() -> @@ -818,12 +880,25 @@ elire_troll() -> Trolls -> Troll = lists:nth(random:uniform(length(Trolls)), Trolls), Troll2 = Troll#troll{date_post = now()}, - mnesia:write(Troll2) + 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])) of + [Id] -> Id; + _ -> undefined + end + end + )). + update_version(1) -> mnesia:transform_table(