From 81393a74d648e4830dc8c7c562fa203f50a105f6 Mon Sep 17 00:00:00 2001 From: Ummon Date: Mon, 23 Mar 2015 09:14:41 +0100 Subject: [PATCH] Remove the weekly troll. --- js/chat/commandes.js | 22 +- js/chat/conversation.js | 120 +-- js/chat/conversations.js | 114 ++- js/chat/message.js | 34 +- js/communication.js | 304 +++---- js/pageAdmin.js | 221 +---- js/pageMinichat.js | 98 +- js/util.js | 68 +- modules/Makefile | 101 +-- modules/erl/euphorik_bd.erl | 342 ++----- modules/erl/euphorik_bd_admin.erl | 850 +++++++++--------- modules/erl/euphorik_common.erl | 12 +- modules/erl/euphorik_daemon.erl | 23 +- .../erl/euphorik_minichat_conversation.erl | 48 +- modules/erl/euphorik_protocole.erl | 360 +++----- modules/erl/euphorik_requests.erl | 18 +- modules/erl/euphorik_test.erl | 413 +++++---- modules/include/euphorik_bd.hrl | 243 +++-- modules/include/euphorik_defines.hrl | 4 +- tools/start_tv.erl | 2 +- tools/tools.erl | 48 +- tools/tools.old.rb | 0 22 files changed, 1454 insertions(+), 1991 deletions(-) mode change 100644 => 100755 tools/tools.erl mode change 100644 => 100755 tools/tools.old.rb diff --git a/js/chat/commandes.js b/js/chat/commandes.js index 0c3d23b..871e22b 100644 --- a/js/chat/commandes.js +++ b/js/chat/commandes.js @@ -15,14 +15,14 @@ // // You should have received a copy of the GNU General Public License // along with Euphorik. If not, see . - + /*jslint laxbreak:true */ /** * Permet d'executer des commandes tapées par l'utilisateur. * les commandes sont entrées directement dans la ligne de saisie du message sous la forme : * / - * + * * Voici les commandes supportées : * /nick * Modifie le nick courant @@ -34,7 +34,7 @@ euphorik.Commandes = function(client, pageMinichat, util, formater) { this.pageMinichat = pageMinichat; this.util = util; this.formater = formater; - + // construction du texte d'aide (liste des commandes) de manière statique this.texteAide = "

Commandes

    "; objectEach( @@ -53,14 +53,14 @@ euphorik.Commandes.liste = { description : "Change le nick courant", usage : "/nick ", exec : function(args, client) { - + if (args.length === 0) { return [euphorik.Commandes.statut.erreur_commande, 'Utilisation de la commande : ' + this.usage]; } - + client.nick = args[0]; $("form#posterMessage input.nick").val(client.nick); - + return [euphorik.Commandes.statut.ok, '']; } }, @@ -88,15 +88,15 @@ euphorik.Commandes.liste = { */ euphorik.Commandes.prototype.exec = function(chaine) { chaine = chaine.trim(); - + var fragments = chaine.split(/\s+/); if (fragments.length === 0 || fragments[0].charAt(0) != '/') { return [euphorik.Commandes.statut.pas_une_commande, '']; } - + var commandName = fragments[0].slice(1); var args = fragments.slice(1); - + if (commandName === "") { return [euphorik.Commandes.statut.erreur_commande, 'La commande est vide']; } @@ -111,10 +111,10 @@ euphorik.Commandes.prototype.exec = function(chaine) { ); return [euphorik.Commandes.statut.ok, '']; } - + if (euphorik.Commandes.liste.hasOwnProperty(commandName)) { return euphorik.Commandes.liste[commandName].exec(args, this.client, this.pageMinichat); } - + return [euphorik.Commandes.statut.erreur_commande, 'La commande /' + commandName + ' est inconnue']; }; diff --git a/js/chat/conversation.js b/js/chat/conversation.js index 5321380..8c11752 100644 --- a/js/chat/conversation.js +++ b/js/chat/conversation.js @@ -15,7 +15,7 @@ // // You should have received a copy of the GNU General Public License // along with Euphorik. If not, see . - + /*jslint laxbreak:true */ /** @@ -28,29 +28,29 @@ */ euphorik.Conversation = function(conversations, num) { this.conversations = conversations; - + // peut changer au cours de la vie de la conversation, n'est pas un id ! this.num = num; - + this.id = Math.floor(Math.random() * 1000000).toString(36); - + this.util = this.conversations.util; this.formater = this.conversations.formater; this.client = this.conversations.client; - + this.idDernierMessageAffiche = 0; this.racine = undefined; - + this.messages = []; this.messagesParId = {}; - + this.nbMessageMax = euphorik.conf.nbMessageAffiche; // Le nombre de message affiché par page var messagesXHTML = '
    '; var messageRacineXHTML = '
    '; var reverse = this.client.chatOrder === "reverse"; - - var XHTML = + + var XHTML = '' + (reverse ? messagesXHTML : "") + '
    ' + @@ -64,24 +64,24 @@ euphorik.Conversation = function(conversations, num) { (reverse ? "" : messagesXHTML) + //'
    ' + ''; - + $("#conversations tr").append(XHTML); - + // les infos bulles this.util.infoBulle("Aller à la première page", $("#" + this.getId() + " .titre .numPage"), euphorik.Util.positionBulleType.haut); if (this.num !== 0) { this.util.infoBulle("Créer un lien vers la conversation", $("#" + this.getId() + " .titre .creerLien")); this.util.infoBulle("Close the conversation", $("#" + this.getId() + " .titre .close")); } - + // les différents événements liés à la conversation - var thisConversation = this; + var thisConversation = this; $("#" + this.getId() + " .titre .creerLien").click(function() { thisConversation.util.replaceSelection( $("form#posterMessage input.message")[0], "{" + thisConversation.client.conversations[thisConversation.num - 1].root.toString(36) + "}" ); - }); + }); $("#" + this.getId() + " .titre .close").click(function() { thisConversation.conversations.supprimerConversation(thisConversation.num); }); @@ -118,7 +118,7 @@ euphorik.Conversation.prototype.majRacine = function() { if (!this.racine) { return; } - + if (!(this.racine.id in this.messagesParId)) { this.messagesParId[this.racine.id] = this.racine; var element = $(this.racine.XHTML(true, this.getId())); @@ -176,7 +176,7 @@ euphorik.Conversation.prototype.setPage = function(pageCourante, dernierePage) { */ euphorik.Conversation.prototype.setFunPage = function(funNext, funPrev, funReset) { var thisConversation = this; - + $("#" + this.getId() + " .next").click( function() { funNext(thisConversation.num); } ); @@ -201,12 +201,12 @@ euphorik.Conversation.prototype.getId = function() { euphorik.Conversation.prototype.ajouterMessage = function(message) { this.messages.push(message); this.messagesParId[message.id] = message; - + // enlève le message exedentaire si nécessaire if (this.messages.length > this.nbMessageMax) { delete this.messagesParId[this.messages.shift().id]; } - + // met à jour le membre 'estReponduPar' des messages de la conversation for (var i = 0; i < this.messages.length - 1; i++) { var autreMess = this.messages[i]; @@ -224,7 +224,7 @@ euphorik.Conversation.prototype.viderMessages = function() { this.messagesParId = {}; this.idDernierMessageAffiche = 0; $("#" + this.getId() + " .messages .message").remove(); - + // enlève également la racine $("#" + this.getId() + " .titre .messageRacine").empty(); }; @@ -244,35 +244,35 @@ euphorik.Conversation.prototype.flush = function() { var messagePair = (this.idDernierMessageAffiche === 0 ? true : ($("#" + this.getId() + " .messages div:" + (reverse ? "first" : "last")).attr("class").search("messagePair") === -1) - ); - - // permet d'itérer sur les nouveaux messages à afficher - var pourChaqueNouveauMessage = function(f) { - thisConversation.messages.each(function(i, mess) { - if (mess.id > thisConversation.idDernierMessageAffiche) { - f(mess); - } - }); + ); + + // permet d'itérer sur les nouveaux messages à afficher + var pourChaqueNouveauMessage = function(f) { + thisConversation.messages.each(function(i, mess) { + if (mess.id > thisConversation.idDernierMessageAffiche) { + f(mess); + } + }); }; - + // construction de l'XHTML des messages - var XHTML = ""; + var XHTML = ""; pourChaqueNouveauMessage(function(mess) { XHTML += mess.XHTML(messagePair, thisConversation.getId()); messagePair = !messagePair; }); - - var DOM = $(XHTML); - + + var DOM = $(XHTML); + // pour chaque nouveau message au niveau du document on lui assigne ses événements DOM.each(function() { thisConversation.attacherEventsSurMessage(this); }); - + if (reverse) { DOM.prependTo("#" + this.getId() + " .messages"); } else { DOM.appendTo("#" + this.getId() + " .messages"); } - + // enlève les messages exedentaires au niveau du document var nbMessagesAffiche = $("#" + this.getId() + " .messages .message").size(); if (nbMessagesAffiche > this.nbMessageMax) { @@ -281,26 +281,26 @@ euphorik.Conversation.prototype.flush = function() { } else { $("#" + this.getId() + " .messages .message").slice(0, nbMessagesAffiche - this.nbMessageMax).remove(); } - } - - // met à jour la classe des messages auquels repondent les nouveaux messages - // dans le cas où ce message appartient au client courant (c'est un peu de la triche) TODO : ya mieux ? - pourChaqueNouveauMessage(function(mess) { - if (mess.auteurId === thisConversation.client.id) { - objectEach(mess.repondA, function(messId) { - var mess = thisConversation.messagesParId[messId]; - if (mess) { - mess.clientARepondu = true; - $("#conversations #" + mess.getId(thisConversation.getId())).addClass("repondu"); - } - }); - } + } + + // met à jour la classe des messages auquels repondent les nouveaux messages + // dans le cas où ce message appartient au client courant (c'est un peu de la triche) TODO : ya mieux ? + pourChaqueNouveauMessage(function(mess) { + if (mess.auteurId === thisConversation.client.id) { + objectEach(mess.repondA, function(messId) { + var mess = thisConversation.messagesParId[messId]; + if (mess) { + mess.clientARepondu = true; + $("#conversations #" + mess.getId(thisConversation.getId())).addClass("repondu"); + } + }); + } }); - + if (this.messages.length > 0) { this.idDernierMessageAffiche = this.messages[this.messages.length-1].id; - } - + } + // met à jour la racine de la conversation this.majRacine(); }; @@ -313,11 +313,11 @@ euphorik.Conversation.prototype.flush = function() { euphorik.Conversation.prototype.attacherEventsSurMessage = function(element) { // l'id du message var idMess = this.idMessageFromString($(element).attr("id")); - + if (idMess in this.conversations.messagesRepond) { $(element).addClass("repondEnEvidence"); } - + var thisConversation = this; $(".conversationLink", element).click( function(event) { @@ -327,7 +327,7 @@ euphorik.Conversation.prototype.attacherEventsSurMessage = function(element) { return false; } ); - + $(element).click(function(event) { if ($(event.target).is("a") || $(event.target).parents("#outilsBan").length > 0) { return; @@ -386,7 +386,7 @@ euphorik.Conversation.prototype.attacherEventsSurMessage = function(element) { // les outils de bannissement (uniquement pour les ekMaster) if (thisConversation.client.ekMaster) { $(".nick", element).hover( - function(e) { + function(e) { var userId = parseInt($(".id", this).text(), 10); var nick = $(this); var h = nick.outerHeight(); @@ -422,22 +422,22 @@ euphorik.Conversation.prototype.attacherEventsSurMessage = function(element) { ); } }; - + /** * Etablit une liste des messages à mettre en evidence et des messages à cacher. * Puis applique un plan diabolique. * @param id l'id du message */ -euphorik.Conversation.prototype.afficherConversation = function(id) { +euphorik.Conversation.prototype.afficherConversation = function(id) { var thisConversation = this; - + var message = this.messagesParId[id]; if (!message) { return; } var mess = message.getConversation(this); - + // FIXME : cet appel est très lent $("#" + this.getId() + " .messages .message").each( function() { diff --git a/js/chat/conversations.js b/js/chat/conversations.js index 3f9fe2d..ea7e742 100644 --- a/js/chat/conversations.js +++ b/js/chat/conversations.js @@ -15,7 +15,7 @@ // // You should have received a copy of the GNU General Public License // along with Euphorik. If not, see . - + /*jslint laxbreak:true */ /** @@ -26,16 +26,14 @@ euphorik.Conversations = function(client, formater, util, communication, fragmen this.formater = formater; this.util = util; this.fragment = fragment; - + // un ensemble des messages (id) auquel l'utilisateur répond (vider après l'envoie du message courant) this.messagesRepond = {}; - + this.conversations = []; // les conversations, la première représente la conversation principale - + this.nouvelleConversation(0); - - this.trollIdCourant = 0; - + this.comet = communication.createCometConnection("chat"); }; @@ -47,13 +45,13 @@ euphorik.Conversations.prototype.prefixIdMessage = "rep"; * Permet de définir un message comme étant ou n'étant plus un message auquel l'utilisateur * répond. */ -euphorik.Conversations.prototype.toggleMessageRepond = function(mess) { +euphorik.Conversations.prototype.toggleMessageRepond = function(mess) { // est-ce que l'on répond déjà à ce message ? si oui alors on l'enlève de la liste if (mess.id in this.messagesRepond) { this.enleverMessageRepond(mess); return; } - + this.ajouterMessageRepond(mess); }; @@ -74,11 +72,11 @@ euphorik.Conversations.prototype.mettreAJourFragment = function() { */ euphorik.Conversations.prototype.enleverMessagesRepond = function() { var thisConversations = this; - + objectEach(this.messagesRepond, function(messId, mess) { thisConversations.enleverMessageRepond(mess); }); - + // on réinitialise pour être sur que tout est bien enlevé this.messagesRepond = {}; $("#conversations .message").removeClass("repondEnEvidence"); @@ -100,7 +98,7 @@ euphorik.Conversations.prototype.enleverMessageRepond = function(mess) { */ euphorik.Conversations.prototype.ajouterMessageRepond = function(mess) { var thisConversations = this; - + // est-ce que le message fait partie de la même conversation que les autres messages ? // TODO : solution plus élégante pour prendre un mess parmis messagesRepond !? var mess2; @@ -110,19 +108,19 @@ euphorik.Conversations.prototype.ajouterMessageRepond = function(mess) { break; } } - + if (mess2 && mess2.racineId !== mess.racineId) { this.util.messageDialog("Impossible de répondre à deux messages ne faisant pas partie de la même conversation"); return; } - + $("form#posterMessage #repondA .messages").append(mess.XHTML(undefined, this.prefixIdMessage)); this.messagesRepond[mess.id] = mess; - + // ajout la classe 'repondEnEvidence' au message. comme il peut se trouver potentiellement dans // chaque conversation on construit tous les id potentiels $(mess.getId(this.prefixIdMessage) + ", " + this.exprIdsPotentiels(mess)).addClass("repondEnEvidence"); - + $("#" + mess.getId(this.prefixIdMessage)).click( function() { $(this).fadeOut("normal", function() { @@ -152,9 +150,9 @@ euphorik.Conversations.prototype.exprIdsPotentiels = function(mess) { euphorik.Conversations.prototype.rafraichireNombreMessagesRepond = function() { // TODO : ya pas mieux pour trouver le nombre d'objet ? var nb = objectMemberCount(this.messagesRepond); - + $("#posterMessage #repondA .nb").text(nb); - + var boite = $("#posterMessage #repondA"); if (nb > 0) { boite.show(); @@ -182,22 +180,21 @@ euphorik.Conversations.prototype.getJSONrafraichirMessages = function() { var mess = { "message_count" : euphorik.conf.nbMessageAffiche, "main_page" : this.client.mainConversationPage, - "conversations" : this.getJSONConversations(), - "troll_id" : this.trollIdCourant + "conversations" : this.getJSONConversations() }; - + if (this.client.cookie) { mess.cookie = this.client.cookie; } mess.last_message_id = this.conversations[0].idDernierMessageAffiche; - + return mess; }; euphorik.Conversations.prototype.getJSONConversations = function() { var thisConversations = this; var clientConv = []; - + this.client.conversations.each(function(i, conv) { clientConv.push({ root : conv.root, @@ -218,7 +215,7 @@ euphorik.Conversations.prototype.ajouterMessages = function(elements, numConvers if (!elements.messages.length) { return this.conversations[numConversation] !== undefined; } - + for (var i = 0; i < elements.messages.length; i++) { if (this.ajouterMessage(elements.messages[i], numConversation)) { // si une nouvelle conversation a été créée alors on lui donne la racine @@ -227,15 +224,15 @@ euphorik.Conversations.prototype.ajouterMessages = function(elements, numConvers this.mettreAJourFragment(); } } - + this.flush(numConversation); - + // renseigne la conversation sur la page courante et si c'est la dernière this.conversations[numConversation].setPage( numConversation === 0 ? this.client.mainConversationPage : this.client.conversations[numConversation - 1].page, elements.last_page ); - + return true; }; @@ -246,16 +243,16 @@ euphorik.Conversations.prototype.ajouterMessages = function(elements, numConvers * @param numConversation le numéro de la conversation, 0 = principale * @return true si une nouvelle conversation a été créée sinon false */ -euphorik.Conversations.prototype.ajouterMessage = function(element, numConversation) { - var message = +euphorik.Conversations.prototype.ajouterMessage = function(element, numConversation) { + var message = new euphorik.Message( this.client, this.formater, element ); - + var nouvelleConversation = false; - + if (!this.conversations[numConversation]) { nouvelleConversation = true; this.nouvelleConversation(numConversation); @@ -269,7 +266,7 @@ euphorik.Conversations.prototype.nouvelleConversation = function(num) { var thisConversations = this; this.conversations[num] = new euphorik.Conversation(this, num); - + this.conversations[num].setFunPage( function(num) { // page suivante thisConversations.client.nextPage(num - 1); @@ -285,7 +282,7 @@ euphorik.Conversations.prototype.nouvelleConversation = function(num) { } } ); - + this.ajusterLargeurConversations(); }; @@ -297,7 +294,7 @@ euphorik.Conversations.prototype.supprimerConversation = function(num) { return; // la numéro 0 ne peut être supprimé } this.conversations[num].supprimer(); - + // les numéros sont réassigné for (var i = num; i < this.conversations.length - 1; i++) { this.conversations[i] = this.conversations[i+1]; @@ -305,9 +302,9 @@ euphorik.Conversations.prototype.supprimerConversation = function(num) { } this.conversations.pop(); this.ajusterLargeurConversations(); - + this.client.supprimerConversation(num - 1); - + this.rafraichirMessages(true); this.mettreAJourFragment(); }; @@ -322,7 +319,7 @@ euphorik.Conversations.prototype.ajusterLargeurConversations = function() { // le "- 0.01" evite que IE se chie dessus lamentablement et affiche les conversations les unes au dessus des autres //if($.browser["msie"]) // largeurPourcent -= 0.05 - + $("#conversations td").css("width", largeurPourcent + "%"); }; @@ -338,10 +335,10 @@ euphorik.Conversations.prototype.flushAll = function() { /** * Demande à une conversation de se flusher. */ -euphorik.Conversations.prototype.flush = function(numConv) { +euphorik.Conversations.prototype.flush = function(numConv) { this.conversations[numConv].flush(); }; - + euphorik.Conversations.prototype.ouvrirConversation = function(racine) { if (this.client.ajouterConversation(racine)) { this.rafraichirMessages(true); @@ -363,38 +360,26 @@ euphorik.Conversations.prototype.viderMessages = function() { */ euphorik.Conversations.prototype.rafraichirMessages = function(vider) { var thisConversations = this; - + vider = vider || false; - + if (vider) { this.conversations.each(function(i, conv) { conv.idDernierMessageAffiche = 0; }); } - - thisConversations.util.showWaitBar(); // pour faire patienter le user :) - + + thisConversations.util.showWaitBar(); // pour faire patienter le user :) + this.comet.waitEvent( - function() { return thisConversations.getJSONrafraichirMessages(); }, { - "new_troll" : - function(data) { - thisConversations.trollIdCourant = data.troll_id; - $("#trollCourant .troll").html(thisConversations.formater.completeProcessing(data.content)).unbind("click").click( - function() { - thisConversations.ouvrirConversation(data.message_id); - } - ); - - $("#trollCourant .troll a[@rel*=lightbox]").lightBox(); - }, "new_messages" : - function(data) { - + function(data) { + if (vider) { thisConversations.viderMessages(); } - + // ajoute les messages reçus à leur conversation respective data.conversations.each(function(numConv, conv) { if (!thisConversations.ajouterMessages(conv, numConv)) { @@ -402,15 +387,16 @@ euphorik.Conversations.prototype.rafraichirMessages = function(vider) { thisConversations.client.supprimerConversation(numConv - 1); } }); - + if (vider) { thisConversations.afficherMessagesRepondConversations(); } - - vider = false; - + + vider = false; + thisConversations.util.hideWaitBar(); } - } + }, + function() { return thisConversations.getJSONrafraichirMessages(); } ); }; diff --git a/js/chat/message.js b/js/chat/message.js index 51d84b9..b3e5f12 100644 --- a/js/chat/message.js +++ b/js/chat/message.js @@ -15,7 +15,7 @@ // // You should have received a copy of the GNU General Public License // along with Euphorik. If not, see . - + euphorik.Reponse = function(id, nick, login) { this.id = id; this.nick = nick || ""; @@ -28,7 +28,7 @@ euphorik.Reponse = function(id, nick, login) { euphorik.Message = function(client, formater, element) { this.client = client; this.formater = formater; - + this.id = element.id; this.auteurId = element.user_id; this.racineId = element.root; @@ -36,11 +36,11 @@ euphorik.Message = function(client, formater, element) { this.nick = element.nick; this.login = element.login; this.contenu = element.content; - + // l'ensemble des id des messages qui reponde à ce message, exemple : {45:true, 32:true} (le 'true' ne sert à rien ^_^) // mis à jour au fur à mesure que des messages sont ajoutés aux conversations this.estReponduPar = {}; - + this.appartientAuClient = element.owner; this.clientARepondu = element.answered; this.estUneReponse = element.is_a_reply; @@ -63,7 +63,7 @@ euphorik.Message.prototype.getId = function(pre) { euphorik.Message.prototype.setRepondA = function(repondAJSON) { var thisMessage = this; this.repondA = {}; - + repondAJSON.each(function(i, reponse) { thisMessage.repondA[reponse.id] = new euphorik.Reponse(reponse.id, reponse.nick, reponse.login); }); @@ -81,7 +81,7 @@ euphorik.Message.prototype.getConversation = function(messages) { // les messages faisant partie de la conversation var messagesEnEvidence = {}; messagesEnEvidence[this.id] = 1; - + // parcours en profondeur var f = function(ids, premierNiveau, ensemble, evidence) { objectEach(ids, function(id) { @@ -94,13 +94,13 @@ euphorik.Message.prototype.getConversation = function(messages) { } }); }; - + // remonte le temps f(this.estReponduPar, true, "estReponduPar", 2); - + // descent le temps - f(this.repondA, true, "repondA", 3); - + f(this.repondA, true, "repondA", 3); + return messagesEnEvidence; }; @@ -113,13 +113,13 @@ euphorik.Message.prototype.XHTML = function(messagePair, pre) { messagePair = true; } pre = pre || ""; - + thisMessage = this; - + // construit l'identifiant de la personne - var identifiant = - this.client.nickFormat === "nick" || this.login === "" ? this.formater.completeProcessing(this.nick) : - (this.client.nickFormat === "login" ? this.formater.completeProcessing(this.login) : + var identifiant = + this.client.nickFormat === "nick" || this.login === "" ? this.formater.completeProcessing(this.nick) : + (this.client.nickFormat === "login" ? this.formater.completeProcessing(this.login) : this.formater.completeProcessing(this.nick) + "(" + this.formater.completeProcessing(this.login) +")" ); var XHTMLrepondA = ""; @@ -134,7 +134,7 @@ euphorik.Message.prototype.XHTML = function(messagePair, pre) { if (XHTMLrepondA) { XHTMLrepondA = "" + XHTMLrepondA + ""; } - + return "
    " + "[" + this.date + "]" + "" + this.auteurId + "" + identifiant + "" + - "" + + "" + "" + XHTMLrepondA + "" + this.formater.completeProcessing(this.contenu, this.nick) + "" + diff --git a/js/communication.js b/js/communication.js index 5ef2d88..64ebf40 100644 --- a/js/communication.js +++ b/js/communication.js @@ -19,42 +19,42 @@ // Regroupe la partie communication JSON client -> serveur de euphorik. // Voir : http://dev.euphorik.ch/wiki/euk/Protocole -/** - * Les fonctions debutReq et finReq servent, par exemple, à afficher à l'utilisateur +/** + * Les fonctions debutReq et finReq servent, par exemple, à afficher à l'utilisateur * qu'une communication est en cours. - * @param funError un fonction executée lors d'un réponse 'error' de la part du serveur, peut être redéfinit pour une requête. - * @param funDebutReq fonction appelée au début d'une requête (facultatif) + * @param funError un fonction executée lors d'un réponse 'error' de la part du serveur, peut être redéfinit pour une requête. + * @param funDebutReq fonction appelée au début d'une requête (facultatif) * @param funFinReq fonction appelée à la fin d'une requête (facultatif) */ euphorik.Communication = function(funError, funDebutReq, funFinReq) { - this.funError = funError; - this.funDebutReq = funDebutReq; + this.funError = funError; + this.funDebutReq = funDebutReq; this.funFinReq = funFinReq; -}; - -/** - * Charge un fichier depuis une url et retourne son contenu. - */ -euphorik.Communication.prototype.load = function(url) { - if (this.funDebutReq) { - this.funDebutReq(); - } - var contenu = ""; - $.ajax({async: false, url: url, success : function(page) { contenu += page; }}); - if (this.funFinReq) { - this.funFinReq(); - } - return contenu; }; - -/** - * Effectue une requête JSON auprès du serveur. - * @param action une chaine spécifiant l'action, par exemple "put_message" - * @param json les données à envoyer associé à l'action, par exemple {"cookie" : "LKJDLAKSJBFLKASN", "nick" : "Paul", "content" : "Bonjour", "answer_to" : [] } - * @param funOk la fonction exécuté après réception des données du serveur - * @param funError la fonction exécuté si une erreur arrive (facultatif) - * @param asynchrone true pour une communication asychrone (facultatif, truepar défaut) - * @param paramsSupp un objet contenant des paramètres supplémentaire pour la fonction ajax de jQuery (facultatif) + +/** + * Charge un fichier depuis une url et retourne son contenu. + */ +euphorik.Communication.prototype.load = function(url) { + if (this.funDebutReq) { + this.funDebutReq(); + } + var contenu = ""; + $.ajax({async: false, url: url, success : function(page) { contenu += page; }}); + if (this.funFinReq) { + this.funFinReq(); + } + return contenu; +}; + +/** + * Effectue une requête JSON auprès du serveur. + * @param action une chaine spécifiant l'action, par exemple "put_message" + * @param json les données à envoyer associé à l'action, par exemple {"cookie" : "LKJDLAKSJBFLKASN", "nick" : "Paul", "content" : "Bonjour", "answer_to" : [] } + * @param funOk la fonction exécuté après réception des données du serveur + * @param funError la fonction exécuté si une erreur arrive (facultatif) + * @param asynchrone true pour une communication asychrone (facultatif, truepar défaut) + * @param paramsSupp un objet contenant des paramètres supplémentaire pour la fonction ajax de jQuery (facultatif) */ euphorik.Communication.prototype.requete = function(action, json, funOk, funError, asynchrone, paramsSupp) { var thisCommunication = this; @@ -65,12 +65,12 @@ euphorik.Communication.prototype.requete = function(action, json, funOk, funErro var mess = this.getBase(action); objectEach(json, function(name, val) { mess[name] = val; - }); - - if (this.funDebutReq) { - this.funDebutReq(); + }); + + if (this.funDebutReq) { + this.funDebutReq(); } - + paramsAjax = { async: asynchrone, type: "POST", @@ -78,9 +78,9 @@ euphorik.Communication.prototype.requete = function(action, json, funOk, funErro dataType: "json", data: { action : JSON.stringify(mess) }, success: - function(data) { - if (thisCommunication.funFinReq) { - thisCommunication.funFinReq(); + function(data) { + if (thisCommunication.funFinReq) { + thisCommunication.funFinReq(); } if (data.reply === "error") { if (funError) { @@ -91,131 +91,133 @@ euphorik.Communication.prototype.requete = function(action, json, funOk, funErro } else if (funOk) { funOk(data); } - }, - error: - function(data) { - if (thisCommunication.funFinReq) { - thisCommunication.funFinReq(); - } + }, + error: + function(data) { + if (thisCommunication.funFinReq) { + thisCommunication.funFinReq(); + } } }; - + if (paramsSupp) { objectEach(paramsSupp, function(name, val) { paramsAjax[name] = val; }); } - + jQuery.ajax(paramsAjax); }; - -euphorik.Communication.prototype.createCometConnection = function(name) { - return new Comet(name, this.getBase); -}; + +euphorik.Communication.prototype.createCometConnection = function(name) { + return new Comet(name, this.getBase); +}; euphorik.Communication.prototype.getBase = function(action) { return { "header" : { "action" : action, "version" : euphorik.conf.versionProtocole } }; -}; - -/** - * Permet de gérer les événements (push serveur). - * Principe de fonctionnement : - * - La page courante créer un objet euphorik.Comet en indiquant le nom de la page et la version du protocole. - * - La page courante attend un événement en appelant 'waitEvent' (non-bloquant) et en donnant deux fonctions : - * - 'funSend' une fonction qui renvoie l'objet à envoyer avant l'attente, par exemple {"dernierMess" : 23} - * ("header" et "page" sont automatiquement ajoutés à l'objet) - * - 'funsReceive' un ensemble de fonctions à appeler en fonction du "reply" du serveur, par exemple {"set_nom" : function(data) { print("ok : " + data.nom); } } - * - * l'information envoyée est sous la forme : - * { - * "header" : {"action" : "wait_event", "version" : }, - * "page" : - * [..] - * } - * l'information reçue est sous la forme : - * { - * "reply" : - * [..] - * } - * et sont de type chaine - * - * @page [string] la page courante pour laquelle on écoute des événements (un string) - * @util [int] la version - */ -Comet = function(page, getBase) { - this.page = page; - this.getBase = getBase; - - // l'objet JSONHttpRequest représentant la connexion d'attente - this.attenteCourante = undefined; - - // le multhreading du pauvre, merci javascript de m'offrire autant de primitives pour la gestion de la concurrence... - this.stop = false; -}; - -/** - * Arrête l'attente courante s'il y en a une. - */ -Comet.prototype.stopAttenteCourante = function() { - this.stop = true; - - if (this.attenteCourante) { - this.attenteCourante.abort(); - } -}; - -/** - * Attend un événement lié à la page. Non-bloquant. - * @funSend une fonction renvoyant les données json à envoyer - * @funsReceive est un objet comprenant les fonctions à appeler en fonction du "reply" - * les fonctions acceptent un paramètre correspondant au données reçues. - * exemple : {"new_message" : function(data){ ... }} - */ -Comet.prototype.waitEvent = function(funSend, funsReceive) { - this.stopAttenteCourante(); - this.stop = false; - var thisComet = this; - - // on doit conserver l'ordre des valeurs de l'objet JSON (le serveur les veut dans l'ordre définit dans le protocole) - // TODO : ya pas mieux ? - var dataToSend = this.getBase("wait_event") - dataToSend["page"] = this.page; - - var poulpe = funSend(); - objectEach(poulpe, function(k, v) { - dataToSend[k] = v; - }); - - this.attenteCourante = jQuery.ajax({ - type: "POST", - url: "request", - dataType: "json", - // TODO : doit disparaitre - timeout: 180000, // timeout de 3min. Gros HACK pas beau. FIXME problème décrit ici : http://groups.google.com/group/jquery-en/browse_thread/thread/8724e64af3333a76 - data: { action : JSON.stringify(dataToSend) }, - success: - function(data) { - funsReceive[data.reply](data); - - // rappel de la fonction dans 100 ms - setTimeout(function(){ thisComet.waitEvent2(funSend, funsReceive); }, 100); - }, - error: - function(XMLHttpRequest, textStatus, errorThrown) { - ;; console.log("Connexion perdue dans Comet.prototype.waitEvent() : \n" + textStatus); - setTimeout(function(){ thisComet.waitEvent2(funSend, funsReceive); }, 1000); - } - }); -}; - -/** - * Si un stopAttenteCourante survient un peu n'importe quand il faut imédiatement arreter de boucler. - */ -Comet.prototype.waitEvent2 = function(funSend, funsReceive) { - if (this.stop) { - return; - } - this.waitEvent(funSend, funsReceive); +}; + +/** + * Permet de gérer les événements (push serveur). + * Principe de fonctionnement : + * - La page courante créer un objet euphorik.Comet en indiquant le nom de la page et la version du protocole. + * - La page courante attend un événement en appelant 'waitEvent' (non-bloquant) et en donnant deux fonctions : + * - 'funsReceive' un ensemble de fonctions à appeler en fonction du "reply" du serveur, par exemple {"set_nom" : function(data) { print("ok : " + data.nom); } } + * - 'funSend' une fonction qui renvoie l'objet à envoyer avant l'attente, par exemple {"dernierMess" : 23} + * ("header" et "page" sont automatiquement ajoutés à l'objet) + * + * l'information envoyée est sous la forme : + * { + * "header" : {"action" : "wait_event", "version" : }, + * "page" : + * [..] + * } + * l'information reçue est sous la forme : + * { + * "reply" : + * [..] + * } + * et sont de type chaine + * + * @page [string] la page courante pour laquelle on écoute des événements (un string) + * @util [int] la version + */ +Comet = function(page, getBase) { + this.page = page; + this.getBase = getBase; + + // l'objet JSONHttpRequest représentant la connexion d'attente + this.attenteCourante = undefined; + + // le multhreading du pauvre, merci javascript de m'offrire autant de primitives pour la gestion de la concurrence... + this.stop = false; +}; + +/** + * Arrête l'attente courante s'il y en a une. + */ +Comet.prototype.stopAttenteCourante = function() { + this.stop = true; + + if (this.attenteCourante) { + this.attenteCourante.abort(); + } +}; + +/** + * Attend un événement lié à la page. Non-bloquant. + * @funsReceive est un objet comprenant les fonctions à appeler en fonction du "reply" + * les fonctions acceptent un paramètre correspondant au données reçues. + * exemple : {"new_message" : function(data){ ... }} + * @funSend une fonction renvoyant les données json à envoyer (optional) + */ +Comet.prototype.waitEvent = function(funsReceive, funSend) { + this.stopAttenteCourante(); + this.stop = false; + var thisComet = this; + + // on doit conserver l'ordre des valeurs de l'objet JSON (le serveur les veut dans l'ordre définit dans le protocole) + // TODO : ya pas mieux ? + var dataToSend = this.getBase("wait_event") + dataToSend["page"] = this.page; + + if (funSend !== undefined) { + var tmp = funSend(); + objectEach(tmp, function(k, v) { + dataToSend[k] = v; + }); + } + + this.attenteCourante = jQuery.ajax({ + type: "POST", + url: "request", + dataType: "json", + // TODO : doit disparaitre + timeout: 180000, // timeout de 3min. Gros HACK pas beau. FIXME problème décrit ici : http://groups.google.com/group/jquery-en/browse_thread/thread/8724e64af3333a76 + data: { action : JSON.stringify(dataToSend) }, + success: + function(data) { + funsReceive[data.reply](data); + + // rappel de la fonction dans 100 ms + setTimeout(function(){ thisComet.waitEvent2(funsReceive, funSend); }, 100); + }, + error: + function(XMLHttpRequest, textStatus, errorThrown) { + // console.log("Connexion perdue dans Comet.prototype.waitEvent() : \n" + textStatus); + setTimeout(function(){ thisComet.waitEvent2(funsReceive, funSend); }, 1000); + } + }); +}; + +/** + * Si un stopAttenteCourante survient un peu n'importe quand il faut imédiatement arreter de boucler. + */ +Comet.prototype.waitEvent2 = function(funsReceive, funSend) { + if (this.stop) { + return; + } + this.waitEvent(funsReceive, funSend); }; \ No newline at end of file diff --git a/js/pageAdmin.js b/js/pageAdmin.js index 608a7ec..fe464b8 100644 --- a/js/pageAdmin.js +++ b/js/pageAdmin.js @@ -15,7 +15,7 @@ // // You should have received a copy of the GNU General Public License // along with Euphorik. If not, see . -// +// // La page d'administation, ne peut être accédée que par les ekMaster (admins) /*jslint laxbreak:true */ @@ -23,14 +23,14 @@ euphorik.PageAdmin = function(client, formater, util, communication) { this.name = "admin"; - + this.client = client; this.formater = formater; this.util = util; this.communication = communication; - + this.comet = this.communication.createCometConnection("admin"); - + // a timer which will periodically refresh the banned IP list this.timeoutIDmajIPs = null; }; @@ -39,42 +39,18 @@ euphorik.PageAdmin = function(client, formater, util, communication) { * Interface des pages. */ euphorik.PageAdmin.prototype.contenu = function() { - return '

    Trolls

    ' + - '

    Un troll est un sujet à débat, en général une question, affiché sur la page principale.

    ' + - '

    Chaque semaine un troll est choisi au hasard parmis les trolls proposés et devient le troll de la semaine.

    ' + - '
    ' + - '

    ' + - ' ' + - ' ' + - '

    ' + - '
    ' + - '
    ' + - '

    IPs bannies

    ' + - '
    '; + return '

    IPs bannies

    '; }; /** * Interface des pages. */ -euphorik.PageAdmin.prototype.charger = function() { - $("#page form#nouveauTroll").submit(function(){ return false; }); - +euphorik.PageAdmin.prototype.charger = function() { var thisPage = this; - - // la liste des trolls proposés par les ekMasters - this.trolls = new euphorik.Trolls(this.client, this.util, this.formater, this.communication); - + this.waitEvent(); - + this.majIPs(); - - $("#page form#nouveauTroll input.troll").focus(); - - $("#page form#nouveauTroll button.return").click( - function() { - thisPage.posterTroll(); - } - ); }; /** @@ -82,36 +58,13 @@ euphorik.PageAdmin.prototype.charger = function() { */ euphorik.PageAdmin.prototype.decharger = function() { this.comet.stopAttenteCourante(); - + // supprime le rafraichissement période des ips if (this.timeoutIDmajIPs) { clearTimeout(this.timeoutIDmajIPs); } }; -/** - * Post un troll, le contenu est lu à partir de "input.troll". - */ -euphorik.PageAdmin.prototype.posterTroll = function() { - var thisPageAdmin = this; - - var content = $("#page form#nouveauTroll input.troll").val(); - - content = content.trim(); - if (content === "") { - this.util.messageDialog("Le troll est vide"); - return; - } - - this.communication.requete( - "put_troll", - {"cookie" : this.client.cookie, "content" : content}, - function(data) { - $("#page form#nouveauTroll input.troll").val(""); - } - ); -}; - /** * Met à jour la liste des IP bannies. */ @@ -121,11 +74,11 @@ euphorik.PageAdmin.prototype.majIPs = function() { } var thisPageAdmin = this; - + this.communication.requete( "list_banned_ips", {"cookie" : this.client.cookie}, - function(data) { + function(data) { var XHTML = ""; data.list.each(function(i, ip) { XHTML += '
    ' + ip.ip + '|' + @@ -139,13 +92,13 @@ euphorik.PageAdmin.prototype.majIPs = function() { }); XHTML += 'débannir
    '; }); - + if (data.list.length === 0) { XHTML += '

    Aucune IP bannie

    '; } - + $("#ips").html(XHTML); - + $(".ban").each(function() { var ip = $(".ip", this).html(); $(".deban", this).click( @@ -160,7 +113,7 @@ euphorik.PageAdmin.prototype.majIPs = function() { } ); }); - + // rafraichissement toutes les minutes (je sais c'est mal) // le problème est le rafraichissement des temps restant de bannissement qui doit être fait du coté client thisPageAdmin.timeoutIDmajIPs = setTimeout(function(){ thisPageAdmin.majIPs(); }, 60 * 1000); @@ -173,9 +126,9 @@ euphorik.PageAdmin.prototype.majIPs = function() { */ euphorik.PageAdmin.prototype.deban = function(ip) { var thisPageAdmin = this; - + this.communication.requete( - "unban", + "unban", {"cookie" : this.client.cookie, "ip" : ip} ); }; @@ -185,148 +138,14 @@ euphorik.PageAdmin.prototype.deban = function(ip) { */ euphorik.PageAdmin.prototype.waitEvent = function() { var thisPageAdmin = this; - + this.comet.waitEvent( - function() { return { "last_troll" : thisPageAdmin.trolls.dernierTroll }; }, { - "troll_added" : function(data){ thisPageAdmin.trolls.ajouterTrollEvent(data); }, - "troll_modified" : function(data){ thisPageAdmin.trolls.modifierTrollEvent(data); }, - "troll_deleted" : function(data){ thisPageAdmin.trolls.supprimerTrollEvent(data); }, "banned_ips_refresh" : function(data){ thisPageAdmin.majIPs(); }, "error" : function(data) { - thisTrolls.util.messageDialog(data.error_message); - } - } - ); -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Représente un troll, pas grand chose finalement. - */ -euphorik.Troll = function(content, author) { - this.content = content; - this.author = author; -}; - -/////////////////////////////////////////////////////////////////////////////////////////////////// - -euphorik.Trolls = function(client, util, formater, communication) { - this.client = client; - this.util = util; - this.formater = formater; - this.communication = communication; - this.dernierTroll = 0; - - this.trolls = {}; -}; - -euphorik.Trolls.prototype.ajouterTrollEvent = function(data) { - var thisTrolls = this; - - var XHTML = ""; - data.trolls.each(function(i, trollData) { - var troll = new euphorik.Troll(trollData.content, trollData.author); - var trollId = trollData.troll_id; - thisTrolls.trolls[trollId] = troll; - - XHTML += - '
    ' + - '' + thisTrolls.formater.completeProcessing(troll.content, troll.author) + '' + - ' - ' + thisTrolls.formater.completeProcessing(troll.author) + '' + - (trollData.author_id === thisTrolls.client.id ? 'éditerSupprimer' : '') + - '
    '; - }); - $("#trolls").append(XHTML); - $("#trolls .troll").filter(function() { return parseInt($(this).attr("id").substr(5), 10) > thisTrolls.dernierTroll; }).each( - function() { - var troll = this; - var id = parseInt($(this).attr("id").substr(5), 10); - - $("a[@rel*=lightbox]", this).lightBox(); - - $(this).keypress( - function(e) { - if (e.which === 13) { // return - $(".modifier", this).click(); - } - } - ); - - $(".delTroll", this).click( - function() { - thisTrolls.util.messageDialog( - "Êtes-vous sur de vouloir supprimer le troll \"" + thisTrolls.trolls[id].content + "\" ?", - euphorik.Util.messageType.question, - { - "oui" : function() { - thisTrolls.supprimer(id); - }, - "non" : function(){} - } - ); - } - ); - - $(".editTroll", this).click( - function() { - $("span", troll).css("display", "none"); - $(troll).append( - '

    modifierannuler

    ' - ); - $("form input.content").focus(); - - var virerLeFormulaire = function() { - $('form', troll).remove(); - $('span', troll).css("display", "inline"); - }; - $("span.modifier", troll).click( - function() { - var content = $("form input.content", troll).val(); - virerLeFormulaire(); - thisTrolls.modifier(id, content); - } - ); - $("span.annuler", troll).click( virerLeFormulaire ); - $("form", troll).submit(function(){ return false; }); + thisPage.util.messageDialog(data.error_message); } - ); } ); - - if (data.trolls.length > 0) { - thisTrolls.dernierTroll = data.trolls[data.trolls.length - 1].troll_id; - } -}; - -euphorik.Trolls.prototype.modifierTrollEvent = function(data) { - var thisTrolls = this; - $("#trolls #troll" + data.troll_id + " .content").html(thisTrolls.formater.completeProcessing(data.content, thisTrolls.trolls[data.troll_id].author)); - $("#trolls #troll" + data.troll_id + " a[@rel*=lightbox]").lightBox(); - thisTrolls.trolls[data.troll_id].content = data.content; -}; - -euphorik.Trolls.prototype.supprimerTrollEvent = function(data) { - $("#trolls #troll" + data.troll_id).remove(); -}; - -euphorik.Trolls.prototype.modifier = function(id, content) { - this.communication.requete( - "mod_troll", - {"cookie" : this.client.cookie, "troll_id" : id, "content" : content} - ); -}; - -/** - * Supprime un troll en fonction de son id. - */ -euphorik.Trolls.prototype.supprimer = function(id) { - this.communication.requete( - "del_troll", - {"cookie" : this.client.cookie, "troll_id" : id} - ); -}; +}; \ No newline at end of file diff --git a/js/pageMinichat.js b/js/pageMinichat.js index 86c059e..13f3375 100755 --- a/js/pageMinichat.js +++ b/js/pageMinichat.js @@ -15,18 +15,18 @@ // // You should have received a copy of the GNU General Public License // along with Euphorik. If not, see . - + /*jslint laxbreak:true */ euphorik.PageMinichat = function(client, formater, util, communication) { this.name = "minichat"; - + this.client = client; this.formater = formater; this.util = util; this.communication = communication; this.commandes = new euphorik.Commandes(this.client, this, this.util, this.formater); - + // permet d'éviter d'envoyer plusieurs messages simultanément en pressant // rapidement sur "enter" par exemple this.envoieMessageEnCours = false; @@ -45,14 +45,13 @@ euphorik.PageMinichat.prototype.contenu = function() { ' ' + '

    ' + ''; - - var trollXHTML = '
    Troll de la semaine :
    '; + var conversationXHTML = '
    '; - + if (this.client.chatOrder === "reverse") { - return trollXHTML + formulaireXHTML + conversationXHTML; + return formulaireXHTML + conversationXHTML; } else { - return trollXHTML + conversationXHTML + formulaireXHTML; + return conversationXHTML + formulaireXHTML; } }; @@ -62,20 +61,20 @@ euphorik.PageMinichat.prototype.classes = function() { euphorik.PageMinichat.prototype.charger = function() { thisPage = this; - + $("#posterMessage input.nick").val(this.client.nick); - + // cet appel ne doit pas être fait avant l'appel à 'charger' - this.conversations = new euphorik.Conversations(this.client, this.formater, this.util, this.communication, this.fragment); - + this.conversations = new euphorik.Conversations(this.client, this.formater, this.util, this.communication, this.fragment); + this.chargerConversationsFragment(); - - this.conversations.rafraichirMessages(true); + + this.conversations.rafraichirMessages(true); this.util.setCaretToEnd($("form#posterMessage input.message")[0]); // les outils de bannissement (uniquement pour les ekMaster) - if (this.client.ekMaster) { + if (this.client.ekMaster) { // TODO : augmentation un peu space, à revoir this.util.outilsBan = $( '' + @@ -86,16 +85,15 @@ euphorik.PageMinichat.prototype.charger = function() { ' Avertissement' + '' ); - + this.util.infoBulle("Slap", $("#slap", this.util.outilsBan)); this.util.infoBulle("Kick (" + euphorik.conf.tempsKick + "min)", $("#kick", this.util.outilsBan)); this.util.infoBulle("Ban (" + euphorik.conf.tempsBan / 24 / 60 + " jours)", $("#ban", this.util.outilsBan)); this.util.infoBulle("La raison", $("input", this.util.outilsBan)); } - + // la barre d'outils liée à chaque message - this.util.outilsMessage = $('
    ').prependTo("#page.minichat"); - this.util.infoBulle("Ouvrir la conversation liée au troll de la semaine", $("#trollCourant .troll")); + this.util.outilsMessage = $('
    ').prependTo("#page.minichat"); this.util.infoBulle("Cliquer sur les messages pour les enlevers de la liste", $("form#posterMessage #repondA").hover( function() { @@ -112,7 +110,7 @@ euphorik.PageMinichat.prototype.charger = function() { if ($(e.target).is(".nb")) { thisPage.conversations.enleverMessagesRepond(); } - } + } ), euphorik.Util.positionBulleType.droite ); @@ -145,19 +143,19 @@ euphorik.PageMinichat.prototype.charger = function() { } ); // - + // événements - var nouveauMessage = - function() { + var nouveauMessage = + function() { // captcha anti bot if ($("form#posterMessage input.captcha").val() !== "") { return; } - + var message = $("form#posterMessage input.message").val(); - + // traitement des commandes.. - var retCommandes = thisPage.commandes.exec(message); + var retCommandes = thisPage.commandes.exec(message); switch (retCommandes[0]) { case euphorik.Commandes.statut.pas_une_commande : thisPage.envoyerMessage(message); @@ -168,11 +166,11 @@ euphorik.PageMinichat.prototype.charger = function() { case euphorik.Commandes.statut.ok : $("form#posterMessage input.message").val(""); break; - } - + } + $("form#posterMessage input.message").focus(); }; - + $("form#posterMessage").keypress( function(e) { if (e.which === 13) { // return @@ -180,12 +178,12 @@ euphorik.PageMinichat.prototype.charger = function() { } } ); - + $("form#posterMessage button.return").click(nouveauMessage); - + // interdiction de submiter le formulaire $("form#posterMessage").submit(function(){ return false; }); - + $("input.nick").click( function() { var input = $("input.nick")[0]; @@ -197,8 +195,8 @@ euphorik.PageMinichat.prototype.charger = function() { }; euphorik.PageMinichat.prototype.chargerConversationsFragment = function() { - var thisPageMinichat = this; - + var thisPageMinichat = this; + // attention : "conv" doit être un tableau d'entier try { var conv = this.fragment.getVal("conv"); @@ -211,12 +209,12 @@ euphorik.PageMinichat.prototype.chargerConversationsFragment = function() { ;; console.log(e) } }; - + euphorik.PageMinichat.prototype.decharger = function() { this.conversations.comet.stopAttenteCourante(); - + $("body #smiles").remove(); - + this.fragment.delVal("conv"); }; @@ -224,46 +222,46 @@ euphorik.PageMinichat.prototype.decharger = function() { * Envoie un nouve message donné, le nick utilisé est celui se trouvant * dans la zone de saisie (form#posterMessage input.nick). */ -euphorik.PageMinichat.prototype.envoyerMessage = function(message) { - var thisPageMinichat = this; +euphorik.PageMinichat.prototype.envoyerMessage = function(message) { + var thisPageMinichat = this; var nick = $("form#posterMessage input.nick").val(); // (un nick vide est autorisé) nick = this.formater.formatNick(nick); - + if (nick === euphorik.conf.defaultNick) { - this.util.messageDialog("Le nick ne peut pas être " + euphorik.conf.defaultNick); + this.util.messageDialog("Choose a nickname"); return; } - + message = message.trim(); if (!message) { - this.util.messageDialog("Le message est vide"); + this.util.messageDialog("The message is empty"); return; } - + if (!this.client.authentifie()) { if (!this.client.enregistrement()) { - this.util.messageDialog("login impossible"); + this.util.messageDialog("unable to login"); return; } } - + // évite le double post if (this.envoieMessageEnCours) { this.util.messageDialog("Message en cours d'envoie..."); return; } this.envoieMessageEnCours = true; - + this.client.nick = nick; - + this.communication.requete( "put_message", this.getJSONMessage(this.client.nick, message), function() { $("form#posterMessage input.message").val(""); - thisPageMinichat.conversations.enleverMessagesRepond(); + thisPageMinichat.conversations.enleverMessagesRepond(); thisPageMinichat.envoieMessageEnCours = false; }, function(data) { @@ -284,7 +282,7 @@ euphorik.PageMinichat.prototype.getJSONMessage = function(nick, message) { objectEach(this.conversations.messagesRepond, function(id) { repondA.push(parseInt(id, 10)); }); - + return { "cookie" : this.client.cookie, "nick" : nick, diff --git a/js/util.js b/js/util.js index 7ca5760..5feacb5 100644 --- a/js/util.js +++ b/js/util.js @@ -29,9 +29,9 @@ euphorik.Util = function (formater) { $("#info .close").click(function() { $("#info").slideUp(50); }); - + $("body").append('
    ').append('

    '); - + this.formater = formater; this.bulleActive = true; }; @@ -45,7 +45,7 @@ euphorik.Util.messageType = {informatif: 0, question: 1, erreur: 2}; * @buttons An object where the properties are the labels and the values are functions which will be executed when a button is clicked. * @format [bool] The message should be formated. (see 'formater.js') * @time The time while the message is displayed. -1 for infinity. - */ + */ euphorik.Util.prototype.messageDialog = function(message, type, buttons, format, time) { var thisUtil = this; @@ -55,26 +55,26 @@ euphorik.Util.prototype.messageDialog = function(message, type, buttons, format, if (this.timeoutMessageDialog) { clearTimeout(this.timeoutMessageDialog); } - + var close = function() { $("#info").slideUp(100); }; close(); $("#info .message").html(!thisUtil.formater || !format ? message : thisUtil.formater.completeProcessing(message)); - + switch(type) { case euphorik.Util.messageType.informatif : $("#info #icone").attr("class", "information"); break; case euphorik.Util.messageType.question : $("#info #icone").attr("class", "interrogation"); break; case euphorik.Util.messageType.erreur : $("#info #icone").attr("class", "exclamation"); break; } - + $("#info .buttons").html(""); objectEach(buttons, function(name, bouton) { $("#info .buttons").append("
    " + name + "
    ").find("div:last").click(bouton).click(close); }); - - $("#info").slideDown(200); + + $("#info").slideDown(200); if (time !== -1) { - this.timeoutMessageDialog = setTimeout(close, time || euphorik.conf.tooltipDisplayDefaultTime); + this.timeoutMessageDialog = setTimeout(close, time || euphorik.conf.tooltipDisplayDefaultTime); } }; @@ -93,38 +93,38 @@ euphorik.Util.prototype.afficherBoite = function(boite, cible, positionX, positi var positionCible = cible.offset(); var positionBoite = { left : positionX === euphorik.Util.positionTypeX.gauche ? positionCible.left - boite.width() : - (positionX === euphorik.Util.positionTypeX.gaucheRecouvrement ? positionCible.left - boite.width() + cible.width() : + (positionX === euphorik.Util.positionTypeX.gaucheRecouvrement ? positionCible.left - boite.width() + cible.width() : (positionX === euphorik.Util.positionTypeX.droitelsRecouvrement ? positionCible.left : (positionX === euphorik.Util.positionTypeX.droite ? positionCible.left + cible.width() : positionCible.left + cible.width() / 2 - boite.width() / 2 ))), // centre top : positionY === euphorik.Util.positionTypeY.haut ? positionCible.top - boite.height() : - (positionY === euphorik.Util.positionTypeY.hautRecouvrement ? positionCible.top - boite.height() + cible.height() : + (positionY === euphorik.Util.positionTypeY.hautRecouvrement ? positionCible.top - boite.height() + cible.height() : (positionY === euphorik.Util.positionTypeY.basRecouvrement ? positionCible.top : (positionY === euphorik.Util.positionTypeY.bas ? positionCible.top + cible.height() : positionCible.top + cible.height() / 2 - boite.height() / 2 ))) // centre }; - + // calcul les décalages en x et en y pour éviter que la boite ne sorte de la fenêtre, tient compte de la position des barres de défilement - var marge = 10; + var marge = 10; positionBoite.left = positionBoite.left < marge + window.pageXOffset ? marge + window.pageXOffset : (boite.width() - $(window).width() + (positionBoite.left - window.pageXOffset) + marge > 0 ? $(window).width() - boite.width() - marge + window.pageXOffset : positionBoite.left); positionBoite.top = positionBoite.top < marge + window.pageYOffset ? marge + window.pageYOffset : (boite.height() - $(window).height() + (positionBoite.top - window.pageYOffset) + marge > 0 ? $(window).height() - boite.height() - marge + window.pageYOffset : positionBoite.top); - + boite.css("top", positionBoite.top).css("left", positionBoite.left).show(); }; euphorik.Util.positionBulleType = {haut : 0, droite : 1, bas : 2, gauche : 3}; - -/** - * Affiche ou cache la barre d'attente. - */ -euphorik.Util.prototype.showWaitBar = function() { - $("#waitbar").show(); -}; -euphorik.Util.prototype.hideWaitBar = function() { - $("#waitbar").hide(); -}; + +/** + * Affiche ou cache la barre d'attente. + */ +euphorik.Util.prototype.showWaitBar = function() { + $("#waitbar").show(); +}; +euphorik.Util.prototype.hideWaitBar = function() { + $("#waitbar").hide(); +}; /** * Affiche un info bulle lorsque le curseur survole l'élément donné. @@ -134,11 +134,11 @@ euphorik.Util.prototype.hideWaitBar = function() { */ euphorik.Util.prototype.infoBulle = function(message, element, position) { var thisUtil = this; - var cacherBulle = function() { + var cacherBulle = function() { $("#tooltipArrow").hide(); $("#tooltipMessage").hide(); }; - + position = position || euphorik.Util.positionBulleType.haut; element.hover( @@ -150,16 +150,16 @@ euphorik.Util.prototype.infoBulle = function(message, element, position) { var m = $("#tooltipMessage"); var f = $("#tooltipArrow"); f.removeClass().addClass(position === euphorik.Util.positionBulleType.haut ? "tooltipArrowTop" : - (position === euphorik.Util.positionBulleType.droite ? "tooltipArrowRight" : + (position === euphorik.Util.positionBulleType.droite ? "tooltipArrowRight" : (position === euphorik.Util.positionBulleType.bas ? "tooltipArrowBottom" : "tooltipArrowLeft" ))); - + // remplie le paragraphe de la bulle avec le message $("p", m).html(message); - + // réinitialise la position, évite le cas ou la boite est collé à droite et remplie avec un texte la faisant dépassé // dans ce cas la hauteur n'est pas calculé correctement m.css("top", 0).css("left", 0); - + var positionFleche = { left : position === euphorik.Util.positionBulleType.haut || position === euphorik.Util.positionBulleType.bas ? element.offset().left + element.outerWidth() / 2 - f.width() / 2 : @@ -182,7 +182,7 @@ euphorik.Util.prototype.infoBulle = function(message, element, position) { } else if (positionMessage.left < 0) { positionMessage.left = 0; } - + m.css("top", positionMessage.top).css("left", positionMessage.left).show(); f.css("top", positionFleche.top).css("left", positionFleche.left).show(); }, @@ -233,7 +233,7 @@ euphorik.Util.prototype.replaceSelection = function(input, replaceString) { var selectionStart = input.selectionStart; var selectionEnd = input.selectionEnd; input.value = input.value.substring(0, selectionStart) + replaceString + input.value.substring(selectionEnd); - + if (selectionStart != selectionEnd) { // has there been a selection this.setSelectionRange(input, selectionStart, selectionStart + replaceString.length); } else { // set caret @@ -253,7 +253,7 @@ euphorik.Util.prototype.replaceSelection = function(input, replaceString) { }; /** - * Applies rot13 to a given string. See : http://en.wikipedia.org/wiki/ROT13. + * Applies rot13 to a given string. See : http://en.wikipedia.org/wiki/ROT13. * @param str the string. */ euphorik.Util.prototype.rot13 = function(str) { @@ -268,7 +268,7 @@ euphorik.Util.prototype.rot13 = function(str) { if (pos === ch.length) { return ""; } - + var c = ch.charCodeAt(pos); return String.fromCharCode( c + diff --git a/modules/Makefile b/modules/Makefile index 1e68f2b..f3dc5aa 100755 --- a/modules/Makefile +++ b/modules/Makefile @@ -1,61 +1,62 @@ # coding: utf-8 # For more informations about the modules listed here see : http://dev.euphorik.ch/wiki/euk/Home -# Directory where the compiled modules will be put -rep_ebin = ebin +# Directory where the compiled modules will be put +rep_ebin = ebin -# Directory where the sources are -rep_erl = erl +# Directory where the sources are +rep_erl = erl -# Directory which contains the hrl files (records definition) -rep_include = include +# Directory which contains the hrl files (records definition) +rep_include = include +rep_include_yaws = /usr/lib/yaws/include # Arguments for the compilator -# It's possible to compile in native mode with : -# $ make NATIVE=true -ifdef NATIVE - erlc_params = +native -I $(rep_include) -o $(rep_ebin) $< -else - erlc_params = -I $(rep_include) -o $(rep_ebin) $< -endif - +# It's possible to compile in native mode with : +# $ make NATIVE=true +ifdef NATIVE + erlc_params = +native -I $(rep_include) -I $(rep_include_yaws) -o $(rep_ebin) $< +else + erlc_params = -I $(rep_include) -I $(rep_include_yaws) -o $(rep_ebin) $< +endif + all: $(rep_ebin)/smtp.beam \ -$(rep_ebin)/euphorik_bd.beam \ -$(rep_ebin)/euphorik_minichat_conversation.beam \ -$(rep_ebin)/euphorik_requests.beam \ -$(rep_ebin)/euphorik_protocole.beam \ -$(rep_ebin)/euphorik_daemon.beam \ -$(rep_ebin)/euphorik_bd.beam \ -$(rep_ebin)/euphorik_bd_admin.beam \ -$(rep_ebin)/euphorik_common.beam \ -$(rep_ebin)/euphorik_test.beam +$(rep_ebin)/euphorik_bd.beam \ +$(rep_ebin)/euphorik_minichat_conversation.beam \ +$(rep_ebin)/euphorik_requests.beam \ +$(rep_ebin)/euphorik_protocole.beam \ +$(rep_ebin)/euphorik_daemon.beam \ +$(rep_ebin)/euphorik_bd.beam \ +$(rep_ebin)/euphorik_bd_admin.beam \ +$(rep_ebin)/euphorik_common.beam \ +$(rep_ebin)/euphorik_test.beam $(rep_ebin)/smtp.beam: $(rep_erl)/smtp.erl erlc $(erlc_params) - -$(rep_ebin)/euphorik_bd.beam: $(rep_erl)/euphorik_bd.erl $(rep_include)/euphorik_bd.hrl $(rep_include)/euphorik_defines.hrl - erlc $(erlc_params) - -$(rep_ebin)/euphorik_bd_admin.beam: $(rep_erl)/euphorik_bd_admin.erl $(rep_include)/euphorik_bd.hrl $(rep_include)/euphorik_defines.hrl - erlc $(erlc_params) - -$(rep_ebin)/euphorik_minichat_conversation.beam: $(rep_erl)/euphorik_minichat_conversation.erl $(rep_include)/euphorik_bd.hrl - erlc $(erlc_params) - -$(rep_ebin)/euphorik_requests.beam: $(rep_erl)/euphorik_requests.erl $(rep_include)/euphorik_defines.hrl - erlc $(erlc_params) - -$(rep_ebin)/euphorik_protocole.beam: $(rep_erl)/euphorik_protocole.erl $(rep_include)/euphorik_defines.hrl - erlc $(erlc_params) - -$(rep_ebin)/euphorik_daemon.beam: $(rep_erl)/euphorik_daemon.erl $(rep_include)/euphorik_defines.hrl - erlc $(erlc_params) - -$(rep_ebin)/euphorik_common.beam: $(rep_erl)/euphorik_common.erl - erlc $(erlc_params) - -$(rep_ebin)/euphorik_test.beam: $(rep_erl)/euphorik_test.erl $(rep_include)/euphorik_bd.hrl - erlc $(erlc_params) - -clean: - rm ebin/*.beam + +$(rep_ebin)/euphorik_bd.beam: $(rep_erl)/euphorik_bd.erl $(rep_include)/euphorik_bd.hrl $(rep_include)/euphorik_defines.hrl + erlc $(erlc_params) + +$(rep_ebin)/euphorik_bd_admin.beam: $(rep_erl)/euphorik_bd_admin.erl $(rep_include)/euphorik_bd.hrl $(rep_include)/euphorik_defines.hrl + erlc $(erlc_params) + +$(rep_ebin)/euphorik_minichat_conversation.beam: $(rep_erl)/euphorik_minichat_conversation.erl $(rep_include)/euphorik_bd.hrl + erlc $(erlc_params) + +$(rep_ebin)/euphorik_requests.beam: $(rep_erl)/euphorik_requests.erl $(rep_include)/euphorik_defines.hrl + erlc $(erlc_params) + +$(rep_ebin)/euphorik_protocole.beam: $(rep_erl)/euphorik_protocole.erl $(rep_include)/euphorik_defines.hrl + erlc $(erlc_params) + +$(rep_ebin)/euphorik_daemon.beam: $(rep_erl)/euphorik_daemon.erl $(rep_include)/euphorik_defines.hrl + erlc $(erlc_params) + +$(rep_ebin)/euphorik_common.beam: $(rep_erl)/euphorik_common.erl + erlc $(erlc_params) + +$(rep_ebin)/euphorik_test.beam: $(rep_erl)/euphorik_test.erl $(rep_include)/euphorik_bd.hrl + erlc $(erlc_params) + +clean: + rm ebin/*.beam diff --git a/modules/erl/euphorik_bd.erl b/modules/erl/euphorik_bd.erl index f1d5a60..6c3485d 100755 --- a/modules/erl/euphorik_bd.erl +++ b/modules/erl/euphorik_bd.erl @@ -15,7 +15,7 @@ % % 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. @@ -23,11 +23,11 @@ -module(euphorik_bd). --export([ +-export([ % texte : get_texte/1, get_texte/2, - + % users : nouveau_user/2, nouveau_user/4, @@ -35,14 +35,14 @@ update_date_derniere_connexion/1, update_ip/2, update_pseudo_user/2, - user_by_cookie/1, - user_by_id/1, + user_by_cookie/1, + user_by_id/1, user_by_login/1, user_by_login_password/2, user_by_mess/1, css_from_user_cookie/1, is_ek_master_from_cookie/1, - + % messages : nouveau_message/3, nouveau_message_sys/1, @@ -60,26 +60,16 @@ est_une_reponse_a_user/2, a_repondu_a_message/2, possede_message/2, - + % ip : 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, - + % utiles : - resultat_transaction/1, + resultat_transaction/1, get_tuples/3 % must be in a transaction ]). -import(qlc, [e/2, q/1, cursor/2]). @@ -111,8 +101,8 @@ nouveau_user(Cookie, Profile) -> User end, resultat_transaction(mnesia:transaction(F)). - - + + % Ajoute un nouveau user et le renvoie nouveau_user(Login, Password, Cookie, Profile) -> F = fun() -> @@ -122,7 +112,7 @@ nouveau_user(Login, Password, Cookie, Profile) -> User end, resultat_transaction(mnesia:transaction(F)). - + % Définit les données du profile d'une utilisateur. set_profile(Cookie, Login, Password, Profile) -> @@ -174,13 +164,13 @@ update_ip(User_id, IP) -> mnesia:abort("update_ip: User inconnu") end end - ). - - + ). + + % Met à jour le pseudo du user update_pseudo_user(UserId, Pseudo) -> mnesia:transaction( - fun() -> + fun() -> case mnesia:wread({user, UserId}) of [#user{profile = Profile} = User] when Profile#profile.pseudo =/= Pseudo -> mnesia:write(User#user{profile = Profile#profile { pseudo = Pseudo } }); @@ -189,7 +179,7 @@ update_pseudo_user(UserId, Pseudo) -> end end ). - + % Est-ce qu'un utilisateur existe en fonction de son cookie ? % Renvoie {ok, User} ou erreur @@ -202,8 +192,8 @@ user_by_cookie(Cookie) -> end end )). - - + + user_by_id(ID) -> resultat_transaction(mnesia:transaction( fun() -> @@ -217,8 +207,8 @@ user_by_id(ID) -> end end )). - - + + user_by_login(Login) -> resultat_transaction(mnesia:transaction( fun() -> @@ -229,24 +219,24 @@ user_by_login(Login) -> end end )). - + % Renvoie une chaine représentant le cookie ou undefined si pas trouvé. css_from_user_cookie(Cookie) -> - case user_by_cookie(Cookie) of + case user_by_cookie(Cookie) of {ok, #user{profile = Profile}} -> Profile#profile.css; _ -> undefined end. - - + + is_ek_master_from_cookie(Cookie) -> case user_by_cookie(Cookie) of {ok, #user{ek_master = true}} -> true; _ -> false end. - + user_by_login_password(Login, Password) -> resultat_transaction(mnesia:transaction( @@ -257,8 +247,8 @@ user_by_login_password(Login, Password) -> end end )). - - + + % Renvoie {ok, User} où User est un #user possédant le message donné. user_by_mess(Id) -> resultat_transaction(mnesia:transaction( @@ -269,26 +259,26 @@ user_by_mess(Id) -> end end )). - - + + % Ajoute un message. Repond_A est une liste d'id auquel le message répond % retourne soit l'id du message soit {erreur, }. nouveau_message(Mess, Auteur_id, Repond_A_ids) -> % regarde si les id 'Repond_A' existent - F = fun() -> + F = fun() -> Repond_a = lists:foldr( fun(Repond_a_id, Acc) -> - case mnesia:read({minichat, Repond_a_id}) of + case mnesia:read({minichat, Repond_a_id}) of [M] -> [M | Acc]; - _ -> Acc % le message n'est pas trouvé + _ -> Acc % le message n'est pas trouvé end - end, + end, [], Repond_A_ids ), Racine_id = case Repond_a of [] -> undefined; - [M | _] -> + [M | _] -> Une_racine = M#minichat.racine_id, % vérification que tout les messages de Repond_a possède la même racine (même conversation) case lists:all(fun(R) -> R#minichat.racine_id =:= Une_racine end, Repond_a) of @@ -297,9 +287,9 @@ nouveau_message(Mess, Auteur_id, Repond_A_ids) -> _ -> {erreur, "Les messages ne font pas partie de la même conversation"} end - end, - if length(Repond_a) =/= length(Repond_A_ids) -> - {erreur, "Un ou plusieurs messages introuvable"}; + end, + if length(Repond_a) =/= length(Repond_A_ids) -> + {erreur, "Un ou plusieurs messages introuvable"}; true -> case Racine_id of {erreur, E} -> {erreur, E}; @@ -321,7 +311,7 @@ nouveau_message(Mess, Auteur_id, Repond_A_ids) -> 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."); Auteur#user.indice_flood =:= ?INDICE_SPAM_MAX, Delta =< ?DUREE_BLOCAGE_SPAM -> {erreur, "Bloqué pour cause de flood"}; - true -> + true -> mnesia:write(Auteur_maj), Id = nouvel_id(minichat), inserer_reponses(Id, Repond_A_ids), @@ -338,19 +328,19 @@ nouveau_message(Mess, Auteur_id, Repond_A_ids) -> _ -> {erreur, "L'auteur du message est introuvable"} end - end + end end end, resultat_transaction(mnesia:transaction(F)). - + % Définit Id_repondant comme étant la réponse à Ids. Ids est une liste d'id. inserer_reponses(Id_repondant, [Id_mess | Reste]) -> mnesia:write(#reponse_minichat{repondant = Id_repondant, cible = Id_mess}), inserer_reponses(Id_repondant, Reste); inserer_reponses(_, []) -> ok. - - + + % Permet de créer un message système. % Renvoie l'id du message système nouveau_message_sys(Mess) -> @@ -362,62 +352,51 @@ nouveau_message_sys(Mess) -> Id end )). - - + + % Renvoie N messages se trouvant sur la première page -messages(N) -> +messages(N) -> messages(N, 1). -% Renvoie N messages se trouvant sur la page P +% Renvoie N messages se trouvant sur la page P % TODO FIXME : fonction en O(N * P) ! messages(N, P) -> - F = fun() -> - % % #minichat{contenu = contenu_message(E)} + F = fun() -> get_tuples(minichat, mnesia:table_info(minichat, size) - N * P + 1, N) end, - resultat_transaction(mnesia:transaction(F)). - - -get_tuples(Table, First_id, N) -> - lists:foldr( - fun(Id, Acc) -> - case mnesia:read({Table, Id}) of - [T] -> [T | Acc]; - _ -> Acc - end - end, - [], - lists:seq(First_id, First_id + N - 1) - ). - + resultat_transaction(mnesia:transaction(F)). + + +get_tuples(Table, First_id, N) -> + lists:foldr( + fun(Id, Acc) -> + case mnesia:read({Table, Id}) of + [T] -> [T | Acc]; + _ -> Acc + end + end, + [], + lists:seq(First_id, First_id + N - 1) + ). + % Renvoie les messages manquants pour la page P en sachant qu'il y a N message % par page et que le dernier message que l'on possède est Id messages(Id, N, P) -> lists:filter(fun (M) -> M#minichat.id > Id end, messages(N, P)). - - + + % Renvoie {ok, #minichat} (voir #minichat de euphorik_bd.hrl) à partir de son id. message_by_id(Id) -> resultat_transaction(mnesia:transaction( fun() -> case mnesia:read({minichat, Id}) of - [M] -> {ok, M#minichat{contenu = contenu_message(M)}}; + [M] -> {ok, M}; _ -> erreur end end )). - - -% Renvoie le contenu d'un message donnée en fonction du troll associé, à utiliser à l'intérieur d'une transaction. -% TODO : Cette fonction pourrait être remplacée par un "outer-join", est-ce possible avec qlc ? -contenu_message(E) -> - case mnesia:index_read(troll, E#minichat.id, #troll.id_minichat) 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) ? @@ -432,7 +411,7 @@ messages_by_ids(Ids) -> ),[{tmpdir, ?KEY_SORT_TEMP_DIR}]) end )). - + % Est-ce qu'un message existe ? Renvoie un boolean. % TODO : ya pas plus simple ? @@ -440,16 +419,16 @@ message_existe(Id) -> resultat_transaction(mnesia:transaction(fun() -> 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() -> e(q([E || E <- mnesia:table(reponse_minichat)]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) end, resultat_transaction(mnesia:transaction(F)). - - + + % Renvoie les messages auquel M_id répond. parents(M_id) -> resultat_transaction(mnesia:transaction( @@ -461,8 +440,8 @@ parents(M_id) -> M#minichat.id =:= R#reponse_minichat.cible]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) end )). - - + + % Renvoie les message qui repondent à M_id enfants(M_id) -> resultat_transaction(mnesia:transaction( @@ -474,8 +453,8 @@ enfants(M_id) -> M#minichat.id =:= R#reponse_minichat.repondant]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) end )). - - + + % Renvoie les id des parents d'un message M (les messages auquels répond M) % ordrés du plus petit au plus grand.. % On évite d'utiliser qlc pour des raisons de performance @@ -488,11 +467,11 @@ parents_id(M_id) -> fun(#reponse_minichat{cible = Cible}) -> Cible end, Parents )); - _ -> [] + _ -> [] end end)). - - + + % Renvoie les id des enfants d'un message M (les messages qui répondent à M) % ordrés du plus petit au plus grand. % @spec enfants_id(integer()) -> [integer()] @@ -504,10 +483,10 @@ enfants_id(M_id) -> fun(#reponse_minichat{repondant = Repondant}) -> Repondant end, Enfants )); - _ -> [] + _ -> [] end end)). - + % Est-ce que le message Id_mess est une réponse d'une message de Id_user ? % On evite d'utiliser qlc (ce qui était fait avant) pour des raisons de performance. @@ -530,7 +509,7 @@ est_une_reponse_a_user(Id_user, Id_mess) -> end )). - + % Est-ce que Id_user à répondu au message Id_mess % On evite d'utiliser qlc (ce qui était fait avant) pour des raisons de performance. a_repondu_a_message(Id_user, Id_mess) -> @@ -551,8 +530,8 @@ a_repondu_a_message(Id_user, Id_mess) -> end end )). - - + + % Est-ce que Id_user possède Id_mess ? possede_message(Id_user, Id_mess) -> case mnesia:transaction( @@ -563,8 +542,8 @@ possede_message(Id_user, Id_mess) -> {atomic, [Id_user | []]} -> true; _ -> false end. - - + + % 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) @@ -597,13 +576,13 @@ ban(IP, Duration) -> end 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) -> @@ -629,8 +608,8 @@ est_banni(User_id) -> end end )). - - + + % Ban est une date tel que retourner par now(). % Ban_duration est un temps en minutes. % retourne une date. @@ -638,16 +617,16 @@ 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 + +% 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. can_register(IP) -> resultat_transaction(mnesia:transaction( fun() -> 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; [T] -> @@ -665,144 +644,8 @@ can_register(IP) -> end 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(troll), - 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), - Id_message = nouveau_message_sys("Troll de la semaine : "), - Troll2 = Troll#troll{date_post = now(), id_minichat = Id_message}, - mnesia:write(Troll2) - end - end - ). - - % Renvoie le résultat d'une transaction (en décomposant le tuple fournit) resultat_transaction({_, T}) -> T. @@ -811,4 +654,3 @@ resultat_transaction({_, T}) -> % Renvoie un nouvel id pour une table donnée nouvel_id(Table) -> mnesia:dirty_update_counter(counter, Table, 1). - \ No newline at end of file diff --git a/modules/erl/euphorik_bd_admin.erl b/modules/erl/euphorik_bd_admin.erl index 4a559c8..31be4b4 100644 --- a/modules/erl/euphorik_bd_admin.erl +++ b/modules/erl/euphorik_bd_admin.erl @@ -1,226 +1,222 @@ -% 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 . -% -% Module mettant à disposition tout un tas de fonction pour l'administration de la base de données euphorik comme : -% - Création de la BD -% - Mise à jour de la BD -% - Backup et restauration -% - Copie de la BD à partir d'un autre noeud -% -% @author G.Burri - - --module(euphorik_bd_admin). --export([ - version_bd/0, - create/0, - connect/0, - connect/1, - reset/0, - update/0, - - backup/1, - restore/1, - change_node_name/4, - - toggle_ek_master/1, - print_users/0, - print_users/1, - print_user/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"). - - -% Renvoie la version courante de la BD. -version_bd() -> - euphorik_bd:resultat_transaction(mnesia:transaction( - fun() -> - mnesia:read({proprietes, version}) - end - )). - - -% Instructions pour créer une nouvelle base : -% $erl -sname yaws -mnesia dir '"/projets/euphorik/BD"' -% voir doc/installation.txt -% >l(euphorik_bd). -% >euphorik_bd:create(). -create() -> - mnesia:stop(), - mnesia:delete_schema([node()]), - mnesia:create_schema([node()]), % nécessaire pour les tables sur disc - 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(texte, [ - {attributes, record_info(fields, texte)}, - {disc_copies, [node()]} - ]), - mnesia:create_table(minichat, [ - {type, ordered_set}, - {attributes, record_info(fields, minichat)}, - {disc_copies, [node()]} - ]), - mnesia:create_table(reponse_minichat, [ - {type, bag}, - {attributes, record_info(fields, reponse_minichat)}, - {disc_copies, [node()]} - ]), - mnesia:create_table(user, [ - {attributes, record_info(fields, user)}, - {disc_copies, [node()]} - ]), - mnesia:create_table(ip_table, [ - {attributes, record_info(fields, ip_table)}, - {disc_copies, [node()]} - ]), - mnesia:create_table(troll, [ - {attributes, record_info(fields, troll)}, - {disc_copies, [node()]} - ]), - creer_indexes(). - - -% mis à part car lors de la reprise de données avec load_textfile les indexes ne sont pas recréés -creer_indexes() -> - % commence par supprimer les anciens indexes - lists:foreach(fun(T) -> - lists:foreach(fun(P) -> - mnesia:del_table_index(T, P) - end, - mnesia:table_info(T, index) - ) - end, - ?TABLES - ), - mnesia:add_table_index(minichat, auteur_id), - mnesia:add_table_index(reponse_minichat, cible), - mnesia:add_table_index(user, cookie), - mnesia:add_table_index(user, login), +% 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 . +% +% Module mettant à disposition tout un tas de fonction pour l'administration de la base de données euphorik comme : +% - Création de la BD +% - Mise à jour de la BD +% - Backup et restauration +% - Copie de la BD à partir d'un autre noeud +% +% @author G.Burri + + +-module(euphorik_bd_admin). +-export([ + version_bd/0, + create/0, + connect/0, + connect/1, + reset/0, + update/0, + + backup/1, + restore/1, + change_node_name/4, + + toggle_ek_master/1, + print_users/0, + print_users/1, + print_user/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"). + + +% Renvoie la version courante de la BD. +version_bd() -> + euphorik_bd:resultat_transaction(mnesia:transaction( + fun() -> + mnesia:read({proprietes, version}) + end + )). + + +% Instructions pour créer une nouvelle base : +% $erl -sname yaws -mnesia dir '"/projets/euphorik/BD"' +% voir doc/installation.txt +% >l(euphorik_bd). +% >euphorik_bd:create(). +create() -> + mnesia:stop(), + mnesia:delete_schema([node()]), + mnesia:create_schema([node()]), % nécessaire pour les tables sur disc + 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(texte, [ + {attributes, record_info(fields, texte)}, + {disc_copies, [node()]} + ]), + mnesia:create_table(minichat, [ + {type, ordered_set}, + {attributes, record_info(fields, minichat)}, + {disc_copies, [node()]} + ]), + mnesia:create_table(reponse_minichat, [ + {type, bag}, + {attributes, record_info(fields, reponse_minichat)}, + {disc_copies, [node()]} + ]), + mnesia:create_table(user, [ + {attributes, record_info(fields, user)}, + {disc_copies, [node()]} + ]), + mnesia:create_table(ip_table, [ + {attributes, record_info(fields, ip_table)}, + {disc_copies, [node()]} + ]), + % This table is not used anymore. + mnesia:create_table(troll, [ + {attributes, record_info(fields, troll)}, + {disc_copies, [node()]} + ]), + creer_indexes(). + + +% mis à part car lors de la reprise de données avec load_textfile les indexes ne sont pas recréés +creer_indexes() -> + % commence par supprimer les anciens indexes + lists:foreach(fun(T) -> + lists:foreach(fun(P) -> + mnesia:del_table_index(T, P) + end, + mnesia:table_info(T, index) + ) + end, + ?TABLES + ), + mnesia:add_table_index(minichat, auteur_id), + mnesia:add_table_index(reponse_minichat, cible), + mnesia:add_table_index(user, cookie), + mnesia:add_table_index(user, login), mnesia:add_table_index(troll, date_post), - mnesia:add_table_index(troll, id_minichat). - - -% Connexion à la base de données de yaws sur overnux -connect() -> - connect(yaws@flynux). -connect(Node) -> - mnesia:start(), - mnesia:change_config(extra_db_nodes, [Node]). - - -% Efface toutes les données de la base de données. -reset() -> - mnesia:clear_table(counter), - mnesia:clear_table(proprietes), - mnesia:clear_table(texte), - 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:write(#proprietes{nom = version, valeur = ?VERSION_BD}), - User = #user{id = 0, profile = #profile{pseudo = "Sys"}, login = "Sys", date_creation = now(), date_derniere_connexion = now(), ek_master = true}, - mnesia:write(User), - User - end), - peupler_texte(). - - -peupler_texte() -> - mnesia:transaction(fun() -> - mnesia:write(#texte{ id = 10, fr = "Login déjà existant"}), - mnesia:write(#texte{ id = 20, fr = "Trop de register (flood)"}), - mnesia:write(#texte{ id = 30, fr = "Couple login/pass introuvable"}), - mnesia:write(#texte{ id = 40, fr = "Authentification impossible par cookie"}), - mnesia:write(#texte{ id = 50, fr = "Impossible de mettre à jour le profile"}), - mnesia:write(#texte{ id = 60, fr = "timeout"}), - mnesia:write(#texte{ id = 70, fr = "Page inconnue"}), - mnesia:write(#texte{ id = 80, fr = "Vous êtes banni pour encore ~s"}), - mnesia:write(#texte{ id = 90, fr = "Message vide"}), - mnesia:write(#texte{ id = 100, fr = "Impossible d'ajouter un nouveau message. Raison : ~s"}), - mnesia:write(#texte{ id = 110, fr = "Utilisateur inconnu"}), - mnesia:write(#texte{ id = 120, fr = "Il n'est pas possible de s'auto bannir"}), - mnesia:write(#texte{ id = 130, fr = "L'utilisateur est lui même un ekMaster"}), - mnesia:write(#texte{ id = 140, fr = "Utilisateur à bannir inconnu"}), - mnesia:write(#texte{ id = 150, fr = "Utilisateur inconnu ou non ek master"}), - mnesia:write(#texte{ id = 160, fr = "Utilisateur à slaper inconnu"}), - mnesia:write(#texte{ id = 170, fr = "Utilisateur inconnu ou non ek master"}), - mnesia:write(#texte{ id = 180, fr = "Le nombre de troll maximum par utilisateur est atteint : ~w "}), - mnesia:write(#texte{ id = 190, fr = "Le nombre de troll maximum en attente est atteint : ~w "}), - mnesia:write(#texte{ id = 200, fr = "Seul les ekMaster peuvent proposer des trolls"}), - mnesia:write(#texte{ id = 210, fr = "Vous ne posséder pas ce troll"}), - mnesia:write(#texte{ id = 220, fr = "Seul les ekMaster peuvent proposer des trolls"}), - mnesia:write(#texte{ id = 230, fr = "Seul les ekMaster peuvent connaitre la liste des ips bannies"}) - end). - - -% Met à jour la bd, compare ?VERSION_BD avec la version dans la table 'proprietes' -% et exécute les patchs nécessaires. -update() -> - case mnesia:dirty_read({proprietes, version}) of - [#proprietes{valeur = Version}] -> - update(Version); - _ -> - erreur - 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(fichier_backup(Version)) of - ok -> - case patch(Version) of - ok -> - mnesia:dirty_write(#proprietes{nom = version, valeur = Version + 1}), - update(Version + 1); - Erreur -> - 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 + mnesia:add_table_index(troll, id_minichat). + + +% Connexion à la base de données de yaws sur overnux +connect() -> + connect(yaws@flynux). +connect(Node) -> + mnesia:start(), + mnesia:change_config(extra_db_nodes, [Node]). + + +% Efface toutes les données de la base de données. +reset() -> + mnesia:clear_table(counter), + mnesia:clear_table(proprietes), + mnesia:clear_table(texte), + 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:write(#proprietes{nom = version, valeur = ?VERSION_BD}), + User = #user{id = 0, profile = #profile{pseudo = "Sys"}, login = "Sys", date_creation = now(), date_derniere_connexion = now(), ek_master = true}, + mnesia:write(User), + User + end), + peupler_texte(). + + +peupler_texte() -> + mnesia:transaction(fun() -> + mnesia:write(#texte{ id = 10, fr = "Login déjà existant"}), + mnesia:write(#texte{ id = 20, fr = "Trop de register (flood)"}), + mnesia:write(#texte{ id = 30, fr = "Couple login/pass introuvable"}), + mnesia:write(#texte{ id = 40, fr = "Authentification impossible par cookie"}), + mnesia:write(#texte{ id = 50, fr = "Impossible de mettre à jour le profile"}), + mnesia:write(#texte{ id = 60, fr = "timeout"}), + mnesia:write(#texte{ id = 70, fr = "Page inconnue"}), + mnesia:write(#texte{ id = 80, fr = "Vous êtes banni pour encore ~s"}), + mnesia:write(#texte{ id = 90, fr = "Message vide"}), + mnesia:write(#texte{ id = 100, fr = "Impossible d'ajouter un nouveau message. Raison : ~s"}), + mnesia:write(#texte{ id = 110, fr = "Utilisateur inconnu"}), + mnesia:write(#texte{ id = 120, fr = "Il n'est pas possible de s'auto bannir"}), + mnesia:write(#texte{ id = 130, fr = "L'utilisateur est lui même un ekMaster"}), + mnesia:write(#texte{ id = 140, fr = "Utilisateur à bannir inconnu"}), + mnesia:write(#texte{ id = 150, fr = "Utilisateur inconnu ou non ek master"}), + mnesia:write(#texte{ id = 160, fr = "Utilisateur à slaper inconnu"}), + mnesia:write(#texte{ id = 170, fr = "Utilisateur inconnu ou non ek master"}), + mnesia:write(#texte{ id = 230, fr = "Seul les ekMaster peuvent connaitre la liste des ips bannies"}) + end). + + +% Met à jour la bd, compare ?VERSION_BD avec la version dans la table 'proprietes' +% et exécute les patchs nécessaires. +update() -> + case mnesia:dirty_read({proprietes, version}) of + [#proprietes{valeur = Version}] -> + update(Version); + _ -> + erreur + 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(fichier_backup(Version)) of + ok -> + case patch(Version) of + ok -> + mnesia:dirty_write(#proprietes{nom = version, valeur = Version + 1}), + update(Version + 1); + Erreur -> + 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) -> % Prend un chemin vers la feuille de style de type "css/1/euphorik.css" % et renvoie "styles/1/euphorik.css" - Transforme_css = fun("css" ++ Reste) -> + Transforme_css = fun("css" ++ Reste) -> "styles" ++ Reste; (F) -> F end, @@ -236,10 +232,10 @@ patch(1) -> ) end, F(F, M) - end, + end, % Prend un chemin vers la feuille de style de type "css/1/euphorik.css" % et renvoie "styles/1/euphorik.css" - Transforme_css = fun("css" ++ Reste) -> + Transforme_css = fun("css" ++ Reste) -> "styles" ++ Reste; (F) -> F end, @@ -255,37 +251,37 @@ patch(1) -> ) end, F(F, M) - end, - mnesia:create_table(texte, [ - {attributes, record_info(fields, texte)}, - {disc_copies, [node()]} - ]), - peupler_texte(), - % 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, Login, Password, {profile, Pseudo, Email, Transforme_css(Css), Nick_format, View_times, View_tooltips, light, reverse, lists:map(fun({R, _}) -> {R, false} end, Conversations)}, Date_creation, Date_derniere_connexion, Indice_flood, Ek_master, Last_ip} - end, - record_info(fields, 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) - ), - case mnesia:transaction( - fun() -> - % met à jour les enfants des racines - % idéalement : utiliser un cursor mais je crois qu'il n'est pas possible de faire des modifs en itérant en même temps avec un cursor, a voir.. - Messages = e(q([M || M <- mnesia:table(minichat), euphorik_bd:parents(M#minichat.id) =:= []]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]), - lists:foreach(fun(M) -> Traiter_message(M, M#minichat.id) end, Messages) - end - ) of - {aborted, Raison} -> {erreur, Raison}; - {atomic, _} -> ok + end, + mnesia:create_table(texte, [ + {attributes, record_info(fields, texte)}, + {disc_copies, [node()]} + ]), + peupler_texte(), + % 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, Login, Password, {profile, Pseudo, Email, Transforme_css(Css), Nick_format, View_times, View_tooltips, light, reverse, lists:map(fun({R, _}) -> {R, false} end, Conversations)}, Date_creation, Date_derniere_connexion, Indice_flood, Ek_master, Last_ip} + end, + record_info(fields, 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) + ), + case mnesia:transaction( + fun() -> + % met à jour les enfants des racines + % idéalement : utiliser un cursor mais je crois qu'il n'est pas possible de faire des modifs en itérant en même temps avec un cursor, a voir.. + Messages = e(q([M || M <- mnesia:table(minichat), euphorik_bd:parents(M#minichat.id) =:= []]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]), + lists:foreach(fun(M) -> Traiter_message(M, M#minichat.id) end, Messages) + end + ) of + {aborted, Raison} -> {erreur, Raison}; + {atomic, _} -> ok end; % 2 -> 3 patch(2) -> @@ -308,179 +304,179 @@ patch(2) -> {minichat, Id, Auteur_id, Date, Pseudo, Contenu, Racine_id, normal} end, record_info(fields, minichat) - ), - mnesia:transaction(fun() -> - % comble les trous entre les id non-contigues - lists:foreach(fun(Id) -> - case mnesia:read({minichat, Id}) of - [] -> - {ok, #user{profile = Profile}} = euphorik_bd:user_by_id(0), - mnesia:write(#minichat{id = Id, auteur_id = 0, date = undefined, pseudo = Profile#profile.pseudo, contenu = "Comblement...", racine_id = Id, status = deleted}); - _ -> rien - end - end, - lists:seq(1, mnesia:table_info(minichat, size)) - ), - % la table troll utilise maintenant son index et pas celui de la table minichat (correction d'un vieux bug) - mnesia:write(#counter{key = troll, value = mnesia:table_info(minichat, size)}) + ), + mnesia:transaction(fun() -> + % comble les trous entre les id non-contigues + lists:foreach(fun(Id) -> + case mnesia:read({minichat, Id}) of + [] -> + {ok, #user{profile = Profile}} = euphorik_bd:user_by_id(0), + mnesia:write(#minichat{id = Id, auteur_id = 0, date = undefined, pseudo = Profile#profile.pseudo, contenu = "Comblement...", racine_id = Id, status = deleted}); + _ -> rien + end + end, + lists:seq(1, mnesia:table_info(minichat, size)) + ), + % la table troll utilise maintenant son index et pas celui de la table minichat (correction d'un vieux bug) + mnesia:write(#counter{key = troll, value = mnesia:table_info(minichat, size)}) end), - creer_indexes(). % uniquement pour l'indice sur id_minichat de la table troll - - -% Renvoie le dossier dans lequel les backups sont effectué, ce dossier doit être en écriture. -dossier_backups() -> - mnesia:system_info(directory) ++ "/backups/". - - -% Renvoie le fichier (avec le chemin) correspondant à la version Version, par exemple : "/var/euphorik/BD/backups/backup1" -fichier_backup(Version) when is_integer(Version) -> - dossier_backups() ++ "backup" ++ integer_to_list(Version). - - -% crée un backup dont le nom est fournit dans le repertoire backups qui se trouve dans le repertoire de la BD. -backup(Fichier) -> - mnesia:backup(dossier_backups() ++ Fichier). - - -% Restaure un backup de la BD. -restore(Fichier) when is_list(Fichier) -> - mnesia:restore(dossier_backups() ++ Fichier, []); -% 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(Version) when is_integer(Version) -> - mnesia:restore(fichier_backup(Version), []). % [{default_op, recreate_tables}]). - - -% Change le nom du noeud d'un backup. -% provient d'ici : http://www.erlang.org/doc/apps/mnesia/Mnesia_chap7.html#6.9 -% From : l'ancien nom -% To : le nouveau nom -% Source : le nom du fichier de backup -% Target : le nom du fichier du nouveau backup -change_node_name(From, To, Source, Target) -> - Switch = - fun(Node) when Node == From -> To; - (Node) when Node == To -> throw({error, already_exists}); - (Node) -> Node - end, - Convert = - fun({schema, db_nodes, Nodes}, Acc) -> - {[{schema, db_nodes, lists:map(Switch,Nodes)}], Acc}; - ({schema, version, Version}, Acc) -> - {[{schema, version, Version}], Acc}; - ({schema, cookie, Cookie}, Acc) -> - {[{schema, cookie, Cookie}], Acc}; - ({schema, Tab, CreateList}, Acc) -> - Keys = [ram_copies, disc_copies, disc_only_copies], - OptSwitch = - fun({Key, Val}) -> - case lists:member(Key, Keys) of - true -> {Key, lists:map(Switch, Val)}; - false-> {Key, Val} - end - end, - {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc}; - (Other, Acc) -> - {[Other], Acc} - end, - mnesia:traverse_backup(Source, Target, Convert, switched). - - -% Obsolète -%~ backup_text(File) -> - %~ mnesia:dump_to_textfile(File). -%~ restore_text(File) -> - %~ mnesia:stop(), - %~ mnesia:delete_schema([node()]), - %~ mnesia:start(), - %~ create_tables(), - %~ case mnesia:load_textfile(File) of - %~ {atomic, ok} -> - %~ update(), - %~ creer_indexes(); - %~ Erreur -> - %~ Erreur - %~ end. - - -toggle_ek_master(User_id) -> - euphorik_bd:resultat_transaction(mnesia:transaction( - fun() -> - 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}); - _ -> erreur - end - end - )). - - -% 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), - euphorik_bd:resultat_transaction(mnesia:transaction(fun() -> - 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, profile = #profile{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 euphorik_bd: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 euphorik_bd:user_by_id(Id) of - {ok, User} -> - print_user(User); - _ -> - {erreur, "Id pas trouvé : " ++ integer_to_list(Id)} - end. - + creer_indexes(). % uniquement pour l'indice sur id_minichat de la table troll + + +% Renvoie le dossier dans lequel les backups sont effectué, ce dossier doit être en écriture. +dossier_backups() -> + mnesia:system_info(directory) ++ "/backups/". + + +% Renvoie le fichier (avec le chemin) correspondant à la version Version, par exemple : "/var/euphorik/BD/backups/backup1" +fichier_backup(Version) when is_integer(Version) -> + dossier_backups() ++ "backup" ++ integer_to_list(Version). + + +% crée un backup dont le nom est fournit dans le repertoire backups qui se trouve dans le repertoire de la BD. +backup(Fichier) -> + mnesia:backup(dossier_backups() ++ Fichier). + + +% Restaure un backup de la BD. +restore(Fichier) when is_list(Fichier) -> + mnesia:restore(dossier_backups() ++ Fichier, []); +% 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(Version) when is_integer(Version) -> + mnesia:restore(fichier_backup(Version), []). % [{default_op, recreate_tables}]). + + +% Change le nom du noeud d'un backup. +% provient d'ici : http://www.erlang.org/doc/apps/mnesia/Mnesia_chap7.html#6.9 +% From : l'ancien nom +% To : le nouveau nom +% Source : le nom du fichier de backup +% Target : le nom du fichier du nouveau backup +change_node_name(From, To, Source, Target) -> + Switch = + fun(Node) when Node == From -> To; + (Node) when Node == To -> throw({error, already_exists}); + (Node) -> Node + end, + Convert = + fun({schema, db_nodes, Nodes}, Acc) -> + {[{schema, db_nodes, lists:map(Switch,Nodes)}], Acc}; + ({schema, version, Version}, Acc) -> + {[{schema, version, Version}], Acc}; + ({schema, cookie, Cookie}, Acc) -> + {[{schema, cookie, Cookie}], Acc}; + ({schema, Tab, CreateList}, Acc) -> + Keys = [ram_copies, disc_copies, disc_only_copies], + OptSwitch = + fun({Key, Val}) -> + case lists:member(Key, Keys) of + true -> {Key, lists:map(Switch, Val)}; + false-> {Key, Val} + end + end, + {[{schema, Tab, lists:map(OptSwitch, CreateList)}], Acc}; + (Other, Acc) -> + {[Other], Acc} + end, + mnesia:traverse_backup(Source, Target, Convert, switched). + + +% Obsolète +%~ backup_text(File) -> + %~ mnesia:dump_to_textfile(File). +%~ restore_text(File) -> + %~ mnesia:stop(), + %~ mnesia:delete_schema([node()]), + %~ mnesia:start(), + %~ create_tables(), + %~ case mnesia:load_textfile(File) of + %~ {atomic, ok} -> + %~ update(), + %~ creer_indexes(); + %~ Erreur -> + %~ Erreur + %~ end. + + +toggle_ek_master(User_id) -> + euphorik_bd:resultat_transaction(mnesia:transaction( + fun() -> + 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}); + _ -> erreur + end + end + )). + + +% 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), + euphorik_bd:resultat_transaction(mnesia:transaction(fun() -> + 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, profile = #profile{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 euphorik_bd: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 euphorik_bd:user_by_id(Id) of + {ok, User} -> + print_user(User); + _ -> + {erreur, "Id pas trouvé : " ++ integer_to_list(Id)} + end. + diff --git a/modules/erl/euphorik_common.erl b/modules/erl/euphorik_common.erl index 3806df3..e516322 100644 --- a/modules/erl/euphorik_common.erl +++ b/modules/erl/euphorik_common.erl @@ -15,8 +15,8 @@ % % You should have received a copy of the GNU General Public License % along with Euphorik. If not, see . -% -% Module avec plein de bordel utile à l'intérieur. +% +% Module with some useful stuffs. % @author G.Burri @@ -40,7 +40,7 @@ ceiling(X) -> Pos when Pos > 0 -> T + 1; _ -> T end. - + % Retourne la difference entre deux timestamp (erlang:now()) en miliseconde delta_date_ms(D1, D2) -> @@ -50,14 +50,14 @@ delta_date_ms(D1, D2) -> % Retourne la différence entre deux timestamp (erlang:now) en minutes delta_date_minute(D1, D2) -> trunc(delta_date_ms(D1, D2) / 1000 / 60). - + serialize_ip(undefined) -> ""; serialize_ip(IP) -> lists:flatten(io_lib:format("~w.~w.~w.~w", tuple_to_list(IP))). - - + + unserialize_ip(IP) -> case io_lib:fread("~d.~d.~d.~d", IP) of {ok, [A, B, C, D], []} -> {A, B, C, D}; diff --git a/modules/erl/euphorik_daemon.erl b/modules/erl/euphorik_daemon.erl index 68765e7..b802bbe 100755 --- a/modules/erl/euphorik_daemon.erl +++ b/modules/erl/euphorik_daemon.erl @@ -15,9 +15,8 @@ % % You should have received a copy of the GNU General Public License % along with Euphorik. If not, see . -% +% % Module tournant en background s'occupant periodiquement de certaines tâches : -% - sélection du prochain troll chaque semaine % - rechargement des modules lors d'une mise en production % Date : 05.11.2007 % @author G.Burri @@ -32,29 +31,14 @@ start(_A) -> register(euphorik_daemon, self()), loop(). - - + + loop() -> - % on attend une minute de plus pour prevenir une dérive négative (ce qui pourrait engendrer une double élection) receive switch -> % permet de substituer le code du process par un nouveau code, voir reload_euphorik - euphorik_daemon:loop() - after 1000 * (trunc(temps_prochaine_election() + 60)) -> - euphorik_bd:elire_troll(), euphorik_daemon:loop() end. - -% Renvoie le nombre de seconde qu'il reste jusque au prochain lundi à l'heure donnée (l'heure est sur 24heures) -% 86400 est le nombre de seconde dans un jour -temps_prochaine_election() -> - {Date, {H,M,S}} = calendar:local_time(), - Delta = (?JOUR_ELECTION_TROLL - 1) * 86400 + ?HEURE_ELECTION_TROLL * 60 * 60 - -((calendar:day_of_the_week(Date) - 1) * 86400 + H * 60 * 60 + M * 60 + S), - % attention au cas où deux dates (maintenant et la date d'élection) ne se trouvent pas dans la même semaine. - if Delta =< 0 -> Delta + 7 * 86400; true -> Delta end. - - % Recharge tous les modules euphorik. % Appelé lors d'une mise en prod. % TODO : récupérer les noms à partir des .beam dans /modules/ebin @@ -76,4 +60,3 @@ reload_euphorik() -> % changement du code du daemon euphorik_daemon ! switch, ok. - \ No newline at end of file diff --git a/modules/erl/euphorik_minichat_conversation.erl b/modules/erl/euphorik_minichat_conversation.erl index 5cf9a0a..156eaac 100755 --- a/modules/erl/euphorik_minichat_conversation.erl +++ b/modules/erl/euphorik_minichat_conversation.erl @@ -15,23 +15,23 @@ % % You should have received a copy of the GNU General Public License % along with Euphorik. If not, see . -% +% % Ce module permet la gestion des conversations du minichat d'euphorik. % Un message (enfant) peut répondre à des messages (ses parents). % Un message (parent) peut avoir plusieurs réponses (enfants) % @author G.Burri -% +% % Les conversation se compose d'une liste de tuple comprenant la conversation, du premier message de la conversation, % et d'un booleen indiquant s'il y a encore des messages % @type Conversations() = [{Conversation(), Message(), bool()}] -% +% % Une conversation est simplement une liste de messages % @type Conversation() = [Message()] % % Un message est un tuple représentant le message et la liste des id % des messages auquels il répond % @type Message() = {#minichat, [int()]} - + -module(euphorik_minichat_conversation). -export([ @@ -42,8 +42,8 @@ -import(lists, [reverse/1, any/2, map/2, sublist/3, filter/2]). -import(euphorik_bd, [resultat_transaction/1]). -import(mnesia, [table/1, transaction/1]). - - + + % Renvoie les conversations. % Chaque racine est un tuple {R, P, D} % R : l'id de la racine @@ -69,7 +69,7 @@ conversations(Racines, N, D, P) -> true -> mise_en_forme_conversations(Conversations) end. - + % Mise en forme des conversations pour l'utilisateur du module. % @type Conversation_principale() = {[integer()], bool} @@ -78,8 +78,8 @@ conversations(Racines, N, D, P) -> mise_en_forme_conversations([]) -> []; mise_en_forme_conversations([{Principale, Plus_principale} | Conversations]) -> [{mise_en_forme_conversation(Principale), Plus_principale} | map(fun({_, Cn, _, Plus}) -> {mise_en_forme_conversation(Cn), Plus} end, Conversations)]. - - + + % Mise en forme d'une liste d'id de messages : [4, 9, 8, ...] -> [{#minichat, [5, 6]}, ...]. % Ajoute les parents de chaque message. % @spec mise_en_forme_conversation([integer()]) -> [{#minichat, [integer()]}] @@ -101,20 +101,20 @@ mise_en_forme_conversation(Messages) -> end )). - + % Renvoie une liste de conversations, le première élément correspond à la conversation principale. % Les autres éléments sont des tuples {C, Cn, X, Plus}, voir conversation/4 pour plus d'infos. % Racines est une liste de tuple {Id, P} des racines des conversations ou P est la page et Id l'id du message. % @spec conversations_detailees([{integer(), integer()}], integer(), integer(), integer()) -> [[{integer(), bool()}] | Conversation_detailee()] -conversations_detailees(Racines, N, D, P) -> +conversations_detailees(Racines, N, D, P) -> Conversations = map(fun({Racine, P_conv, Dernier}) -> conversation(Racine, N, Dernier, P_conv) end, Racines), - Conversation_principale = resultat_transaction(transaction(fun() -> + Conversation_principale = resultat_transaction(transaction(fun() -> Dernier_id = mnesia:table_info(minichat, size), {CP, Plus} = conversation_principale(Dernier_id, Conversations, N, P), {[M || M <- CP, M > D], Plus} % filtre en fonction de D end)), [Conversation_principale | Conversations]. - + % Construit la conversation principale en fonction d'un id de message initialement placé sur le dernier message % et la liste de conversations. @@ -134,28 +134,28 @@ conversation_principale(Id, Conversations, N, P) -> end, Plus }. - - + + % Id est l'id d'un message, voir ce dessus % 'Messages' sont les messages que l'on doit enlever de la conversation % S est le nombre de messages qu'il faut sauter. % @spec conversation_principale2(integer(), [integer()], integer(), integer()) -> [integer()] conversation_principale2(_, _, 0, _) -> - []; + []; conversation_principale2(0, _, _, _) -> []; -conversation_principale2(Id, Messages, N, S) -> +conversation_principale2(Id, Messages, N, S) -> % traitement message par message (pas des plus performant :/) Doit_etre_saute = any(fun(E) -> E == Id end, Messages), - if Doit_etre_saute -> + if Doit_etre_saute -> conversation_principale2(Id - 1, Messages, N, S); % le message ne fait pas partie de la conversation S =:= 0 -> [Id | conversation_principale2(Id - 1, Messages, N - 1, S)]; % ok : le message fait partie de la conversation true -> conversation_principale2(Id - 1, Messages, N, S - 1) % on n'a pas encore atteint le début de la page end. - - + + % Renvoie un tuple {C, Cn, X, Plus} où % C : La conversation complète % Cn : La conversation tronqué en fonction de N, D et P @@ -181,10 +181,10 @@ conversation(R, N, D, P) -> ) end, reverse(X), - Decalage + N - 1 < length(C) + Decalage + N - 1 < length(C) }. - - + + % Renvoie un tuple {C, X} où C est la conversation complète et X les messages répondant à des mess qui ne font pas partie de la conversation % Attention : les messages de C et de X sont ordrés du plus grand Id au plus petit. % @spec conversation([integer()], [integer()], [integer()]) -> {[int()], [int()]} @@ -201,7 +201,7 @@ conversation(Conv, [M | Reste], X) -> end; conversation(Messages, [], X) -> {Messages, X}. - + % Intersection entre deux listes : [1, 3, 4] n [2, 4, 7] = [4] % @spec intersection(list(term()), list(term())) -> list(term()) diff --git a/modules/erl/euphorik_protocole.erl b/modules/erl/euphorik_protocole.erl index 947a18b..ad80c27 100755 --- a/modules/erl/euphorik_protocole.erl +++ b/modules/erl/euphorik_protocole.erl @@ -15,7 +15,7 @@ % % You should have received a copy of the GNU General Public License % along with Euphorik. If not, see . -% +% % Ce module gére les différents messages envoyés par le client (javascript) via AJAX. % Les messages donnés ainsi que les réponses sont au format JSON. % @author G.Burri @@ -30,9 +30,6 @@ put_message/1, ban/1, slap/1, - put_troll/1, - mod_troll/1, - del_troll/1, unban_ip/1, list_banned_ips/1, erreur/1 @@ -57,7 +54,7 @@ register([{login, Login}, {password, Password}, {profile, Profile_json}], IP) -> erreur_register_flood() end; % Enregistrement sans {Login, Password} -register([{profile, Profile_json}], IP) -> +register([{profile, Profile_json}], IP) -> Can_register = euphorik_bd:can_register(IP), if Can_register -> Profile = profile_from_json(Profile_json), @@ -67,17 +64,17 @@ register([{profile, Profile_json}], IP) -> true -> erreur_register_flood() end. - + erreur_register_flood() -> erreur(20). - + % Un utilisateur se logge (avec un couple {login, mot de passe}) login([{login, Login}, {password, Password}], IP) -> case euphorik_bd:user_by_login_password(Login, Password) of {ok, User} -> loginUser(User, IP); - _ -> + _ -> timer:sleep(?TEMPS_ATTENTE_ERREUR_LOGIN), erreur(30) end; @@ -90,15 +87,15 @@ login([{cookie, Cookie}], IP) -> timer:sleep(?TEMPS_ATTENTE_ERREUR_LOGIN), erreur(40) end. - - + + % L'utilisateur donné se logge avec l'ip donnée. loginUser(User, IP) -> euphorik_bd:update_ip(User#user.id, IP), euphorik_bd:update_date_derniere_connexion(User#user.id), json_reponse_login_ok(User). - - + + % Renvoie un string() représentant un cookie en base 36. Il y a 10^32 possibillités. generer_cookie() -> {A1, A2, A3} = now(), @@ -164,12 +161,12 @@ profile_from_json( Chat_order_valide = lists:any(fun(E) -> E =:= Chat_order end, [reverse, chrono]), if not Chat_order_valide -> {erreur, Chat_order_str ++ " n'est pas une valeur acceptée pour 'chat_order'"}; - true -> + true -> Nick_format = list_to_atom(Nick_format_str), Nick_format_valide = lists:any(fun(E) -> E =:= Nick_format end, [nick, login, nick_login]), if not Nick_format_valide -> {erreur, Nick_format_str ++ " n'est pas une valeur acceptée pour 'nick_format'"}; - true -> + true -> Ostentatious_master = list_to_atom(Ostentatious_master_str), Ostentatious_master_valide = lists:any(fun(E) -> E =:= Ostentatious_master end, [invisible, light, heavy]), if not Ostentatious_master_valide -> @@ -187,7 +184,7 @@ profile_from_json( ostentatious_master = Ostentatious_master } end - end + end end. @@ -199,7 +196,6 @@ wait_event([{page, "chat"} | Data]) -> Last_message_id = case lists:keysearch(last_message_id, 1, Data) of {value, {_, Id}} -> Id; _ -> 0 end, {value, {_, Message_count}} = lists:keysearch(message_count, 1, Data), Main_page = case lists:keysearch(main_page, 1, Data) of {value, {_, P}} -> P; _ -> 1 end, - Troll_id = case lists:keysearch(troll_id, 1, Data) of {value, {_, T}} -> T; _ -> 0 end, {value, {_, {array, Conversations_json}}} = lists:keysearch(conversations, 1, Data), Racines_conversations = lists:map( fun({struct, [{root, Racine}, {page, Page} | Reste]}) -> @@ -212,62 +208,23 @@ wait_event([{page, "chat"} | Data]) -> {ok, U} -> U; _ -> inconnu end, - case {mnesia:subscribe({table, minichat, detailed}), mnesia:subscribe({table, troll, detailed})} of - {{error, E}, _} -> E; - {_, {error, E}} -> E; + case mnesia:subscribe({table, minichat, detailed}) of + {error, E} -> E; _ -> % attente d'événements - R = wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id, Main_page, Troll_id), + R = wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id, Main_page), mnesia:unsubscribe({table, minichat, detailed}), - mnesia:unsubscribe({table, troll, detailed}), R end; % Attend un événement pour la page "Admin" -wait_event([{page, "admin"}, {last_troll, Last_troll}]) -> - case wait_event_page_admin(Last_troll) of +wait_event([{page, "admin"}]) -> + case wait_event_page_admin() of banned_ips_refresh -> - {struct, - [ - {reply, "banned_ips_refresh"} - ] - }; - {mod, Troll} -> {struct, [ - {reply, "troll_modified"}, - {troll_id, Troll#troll.id}, - {content, Troll#troll.content} + {reply, "banned_ips_refresh"} ] }; - {add, Trolls} -> - {struct, - [ - {reply, "troll_added"}, - {trolls, {array, - lists:map( - fun(T) -> - {ok, #user{profile = Profile} = User} = euphorik_bd:user_by_id(T#troll.id_user), - {struct, - [ - {troll_id, T#troll.id}, - {content, T#troll.content}, - {author, Profile#profile.pseudo}, - {author_id, User#user.id} - ] - } - end, - Trolls - ) - }} - ] - }; - {del, Troll_id} -> - {struct, - [ - {reply, "troll_deleted"}, - {troll_id, Troll_id} - ] - }; _ -> erreur(60) end; @@ -275,71 +232,60 @@ wait_event(_) -> erreur(70). -% Attend un événement pour la page "Chat" et renvoie soit un troll soit les messages manquants au client. -wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id, Main_page, Troll_id) -> - % est-ce que le troll est à jour ? - case euphorik_bd:current_troll() of - Current when is_record(Current, troll), Current#troll.id =/= Troll_id -> - {struct, [ - {reply, "new_troll"}, - {troll_id, Current#troll.id}, - {message_id, Current#troll.id_minichat}, - {content, Current#troll.content} - ]}; - _ -> - % est-ce qu'il y a des nouveaux messages ? - case euphorik_minichat_conversation:conversations(Racines_conversations, Message_count, Last_message_id, Main_page) of - vide -> - wait_event_bd_page_chat(), - wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id, Main_page, Troll_id); - Conversations -> - % accrochez-vous ca va siouxer ;) - {struct, [ - {reply, "new_messages"}, - {conversations, {array, - lists:map( - fun({Racine, {Conv, Plus}}) -> - {struct, [ - {last_page, not Plus}, - {first, % le premier message de la conversation - if Racine =:= undefined orelse Conv =:= [] -> - null; - true -> - {Racine_id, _, _} = Racine, - case euphorik_bd:message_by_id(Racine_id) of - {ok, Mess} -> - json_message(Mess, euphorik_bd:parents_id(Racine_id), User); - _ -> - null - end - end - }, - {messages, {array, - lists:map( - fun({Mess, Repond_a}) -> - json_message(Mess, Repond_a, User) - end, - Conv - ) - }} - ]} - end, - % on ajoute un 'undefined' correspondant à la premier conversation qui ne possède pas de racine - % TODO : peut être à revoir car un peu lourd est compliqué - aggregation_racines_conversations([undefined | Racines_conversations], Conversations) - ) - }} - ]} - end +% Attend un événement pour la page "Chat" et renvoie les messages manquants au client. +wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id, Main_page) -> + % est-ce qu'il y a des nouveaux messages ? + case euphorik_minichat_conversation:conversations(Racines_conversations, Message_count, Last_message_id, Main_page) of + vide -> + wait_event_bd_page_chat(), + wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id, Main_page); + Conversations -> + % Accrochez-vous ca va siouxer ;). + {struct, [ + {reply, "new_messages"}, + {conversations, {array, + lists:map( + fun({Racine, {Conv, Plus}}) -> + {struct, [ + {last_page, not Plus}, + {first, % le premier message de la conversation + if Racine =:= undefined orelse Conv =:= [] -> + null; + true -> + {Racine_id, _, _} = Racine, + case euphorik_bd:message_by_id(Racine_id) of + {ok, Mess} -> + json_message(Mess, euphorik_bd:parents_id(Racine_id), User); + _ -> + null + end + end + }, + {messages, {array, + lists:map( + fun({Mess, Repond_a}) -> + json_message(Mess, Repond_a, User) + end, + Conv + ) + }} + ]} + end, + % on ajoute un 'undefined' correspondant à la premier conversation qui ne possède pas de racine + % TODO : peut être à revoir car un peu lourd est compliqué + aggregation_racines_conversations([undefined | Racines_conversations], Conversations) + ) + }} + ]} end. - -aggregation_racines_conversations(L1, L2) -> + +aggregation_racines_conversations(L1, L2) -> aggregation_racines_conversations(L1, L2, []). aggregation_racines_conversations([], [], L) -> lists:reverse(L); aggregation_racines_conversations([E1|R1], [E2|R2], L) -> aggregation_racines_conversations(R1, R2, [{E1, E2} | L]). - + % Attend un événement lié à la page 'chat'. @@ -347,71 +293,42 @@ wait_event_bd_page_chat() -> receive % attente d'un post {mnesia_table_event, {write, minichat, _Message, [], _}} -> ok; - {mnesia_table_event, {write, troll, Troll, [Old_troll | _], _}} when Troll#troll.date_post =/= undefined, Old_troll#troll.date_post == undefined -> - ok; {tcp_closed, _} -> exit(normal); _ -> wait_event_bd_page_chat() % 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 -> + after 1000 * 60 * 60 -> timeout end. % Attent un événement concernant la page admin -% Renvoie les trolls manquants posté après Last_id ou banned_ips_refresh. -% 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 banned_ips_refresh +% banned_ips_refresh % ou timeout -wait_event_page_admin(Last_id) -> - case {mnesia:subscribe({table, troll, detailed}), mnesia:subscribe({table, ip_table, detailed})} of - {{error, E}, _ } -> E; - {_, {error, E}} -> E; - _ -> - R = case euphorik_bd:trolls(Last_id) of - [] -> % pas de trolls - wait_event_page_admin(); - Trolls -> - {add, Trolls} - end, - mnesia:unsubscribe({table, troll, detailed}), - mnesia:unsubscribe({table, ip_table, detailed}), - R - end. - wait_event_page_admin() -> - % 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}; - {mnesia_table_event, {write, ip_table, IP, [Old_IP | _], _}} - when Old_IP#ip_table.ban =/= IP#ip_table.ban; Old_IP#ip_table.ban_duration =/= IP#ip_table.ban_duration -> - banned_ips_refresh; - {tcp_closed, _} -> - exit(normal); + case mnesia:subscribe({table, ip_table, detailed}) of + {error, E} -> E; _ -> - wait_event_page_admin() - % 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 + R = receive + {mnesia_table_event, {write, ip_table, IP, [Old_IP | _], _}} + when Old_IP#ip_table.ban =/= IP#ip_table.ban; Old_IP#ip_table.ban_duration =/= IP#ip_table.ban_duration -> + banned_ips_refresh; + {tcp_closed, _} -> + exit(normal); + _ -> + wait_event_page_admin() + % 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, + mnesia:unsubscribe({table, ip_table, detailed}), + R end. - - + + % Un utilisateur envoie un message % Answer_to est une liste d'id (int) put_message( @@ -480,7 +397,7 @@ ban( _ -> erreur(150) end. - + % slapage d'un user (avertissement) slap( @@ -494,7 +411,7 @@ slap( {ok, User1 = #user{ek_master = true, profile = Profile1}} -> case euphorik_bd:user_by_id(User_id) of {ok, User1} -> - euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s s'auto slap~s.", + euphorik_bd:nouveau_message_sys(lists:flatten(io_lib:format("~s s'auto slap~s.", [ Profile1#profile.pseudo, if Reason =/= [] -> " - Raison: " ++ Reason; true -> "" end @@ -518,75 +435,8 @@ slap( _ -> erreur(170) end. - - -put_troll( - [ - {cookie, Cookie}, - {content, Content} - ] -) -> - % controle que l'utilisateur est un admin - case euphorik_bd:user_by_cookie(Cookie) of - {ok, User = #user{ek_master = true}} -> - case euphorik_bd:put_troll(User#user.id, Content) of - max_troll_reached_per_user -> - erreur(180, [?NB_MAX_TROLL_WAITING_BY_USER]); - max_troll_reached -> - erreur(190, [?NB_MAX_TROLL_WAITING]); - _Id -> - json_reponse_ok() - end; - _ -> - erreur(200) - end. - - -mod_troll( - [ - {cookie, Cookie}, - {troll_id, Troll_id}, - {content, Content} - ] -) -> - % controle que l'utilisateur est un admin - case euphorik_bd:user_by_cookie(Cookie) of - {ok, User = #user{ek_master = true}} -> - User_id = User#user.id, - case euphorik_bd:troll_by_id(Troll_id) of - {ok, #troll{id_user = User_id}} -> - euphorik_bd:mod_troll(Troll_id, Content), - json_reponse_ok(); - _ -> - erreur(210) - end; - _ -> - erreur(220) - end. - -del_troll( - [ - {cookie, Cookie}, - {troll_id, Troll_id} - ] -) -> - % controle que l'utilisateur est un admin - case euphorik_bd:user_by_cookie(Cookie) of - {ok, User = #user{ek_master = true}} -> - User_id = User#user.id, - case euphorik_bd:troll_by_id(Troll_id) of - {ok, #troll{id_user = User_id}} -> - euphorik_bd:del_troll(Troll_id), - json_reponse_ok(); - _ -> - erreur(210) - end; - _ -> - erreur(220) - end. - - + unban_ip( [ {cookie, Cookie}, @@ -600,8 +450,8 @@ unban_ip( _ -> erreur(230) end. - - + + list_banned_ips( [ {cookie, Cookie} @@ -645,12 +495,12 @@ list_banned_ips( % Construit une erreur erreur(Num, Args) -> erreur_json(Num, lists:flatten(io_lib:format(euphorik_bd:get_texte(Num), Args))). - - + + erreur(Num) -> erreur_json(Num, euphorik_bd:get_texte(Num)). - - + + erreur_json(Num, Mess) -> { struct, [ @@ -659,8 +509,8 @@ erreur_json(Num, Mess) -> {error_message, Mess} ] }. - - + + % Formatage de minutes. % par exemple : "1min", "45min", "1h23min", "1jour 2h34min" format_minutes(Min) -> @@ -674,8 +524,8 @@ format_minutes(Min) -> true -> " " ++ integer_to_list(Minutes) ++ " minute" ++ if Minutes > 1 -> "s"; true -> "" end end. - - + + % Formatage d'une heure % local_time() -> string format_date(Date) -> @@ -690,7 +540,7 @@ format_date(Date) -> Hier -> "Hier "; Annee =:= AnneeNow -> - io_lib:format("~2.10.0B/~2.10.0B ", [Jour, Mois]); + io_lib:format("~2.10.0B/~2.10.0B ", [Jour, Mois]); true -> io_lib:format("~2.10.0B/~2.10.0B/~B ", [Jour, Mois, Annee]) end ++ @@ -700,8 +550,8 @@ format_date(Date) -> json_reponse_ok() -> {struct, [{reply, "ok"}]}. - - + + json_reponse_login_ok(#user{profile = Profile} = User) -> { struct, [ @@ -731,7 +581,7 @@ json_reponse_login_ok(#user{profile = Profile} = User) -> }} ] }. - + % Renvoie le message formaté en JSON. % Mess est de type #minichat % Repond_a est une liste d'id des messages auquel répond Mess @@ -754,7 +604,7 @@ json_message(Mess, Repond_a, User) -> {content, Mess#minichat.contenu}, {root, Mess#minichat.racine_id}, {answer_to, {array, lists:map( - fun(Id_mess) -> + fun(Id_mess) -> {ok, M} = euphorik_bd:message_by_id(Id_mess), {ok, User_reponse} = euphorik_bd:user_by_mess(M#minichat.id), {struct, [{id, M#minichat.id}, {nick, M#minichat.pseudo}, {login, User_reponse#user.login}]} diff --git a/modules/erl/euphorik_requests.erl b/modules/erl/euphorik_requests.erl index 3ec70f3..42fb87f 100755 --- a/modules/erl/euphorik_requests.erl +++ b/modules/erl/euphorik_requests.erl @@ -15,7 +15,7 @@ % % You should have received a copy of the GNU General Public License % along with Euphorik. If not, see . -% +% % Ce module est fait pour répondre à des requêtes JSON via 'AJAX'. % Il est définit comme 'appmods' pour l'url "request" dans Yaws. % Par exemple http://www.euphorik.ch/request abouti sur la fonction out/1 de ce module. @@ -24,12 +24,12 @@ -module(euphorik_requests). -export([out/1]). --include_lib("yaws/include/yaws_api.hrl"). +-include_lib("yaws_api.hrl"). -include("../include/euphorik_defines.hrl"). % Point d'entrée pour les requêtes AJAX sur http://www.euphorik.ch/request. -out(A) -> +out(A) -> IP = case inet:peername(A#arg.clisock) of {ok, {Adresse, _Port}} -> Adresse; _ -> inconnue @@ -55,7 +55,7 @@ traiter_message(Contenu, IP) -> ))) end ). - + % Authentification d'un client. traiter_action("authentification", JSON, IP) -> @@ -78,19 +78,9 @@ traiter_action("ban", JSON, _) -> % Un ekMaster slap un utilisateur. traiter_action("slap", JSON, _) -> euphorik_protocole:slap(JSON); -% Un ekMaster envoie un nouveau troll. -traiter_action("put_troll", JSON, _) -> - euphorik_protocole:put_troll(JSON); -% Un ekMaster modifie un troll. -traiter_action("mod_troll", JSON, _) -> - euphorik_protocole:mod_troll(JSON); -% Un ekMaster supprime un troll. -traiter_action("del_troll", JSON, _) -> - euphorik_protocole:del_troll(JSON); % Un ekMaster demande la liste des ips bannies. traiter_action("list_banned_ips", JSON, _) -> euphorik_protocole:list_banned_ips(JSON); % Un ekMaster débannie une ip. traiter_action("unban", JSON, _) -> euphorik_protocole:unban_ip(JSON). - \ No newline at end of file diff --git a/modules/erl/euphorik_test.erl b/modules/erl/euphorik_test.erl index 2cbc1ec..7b2d9a1 100644 --- a/modules/erl/euphorik_test.erl +++ b/modules/erl/euphorik_test.erl @@ -1,212 +1,209 @@ -% 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 . -% -% Module de test de euphorik. -% Crée un certain nombre d'utilisateur et post des messages aléatoire. - - --module(euphorik_test). --export([ - bench_write_minichat/1, - start/2, - stop/1, - bench_get_messages/0, - bench_get_messages_avec_2_conversations/0 -]). --include("../include/euphorik_bd.hrl"). +% 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 . +% +% Module de test de euphorik. +% Crée un certain nombre d'utilisateur et post des messages aléatoire. + + +-module(euphorik_test). +-export([ + bench_write_minichat/1, + start/2, + stop/1, + bench_get_messages/0, + bench_get_messages_avec_2_conversations/0 +]). +-include("../include/euphorik_bd.hrl"). % les intervalles en seconde min en max entre deux postes de message d'un utilisateur % le temps d'attente est choisi au hasard entre ces deux valeurs -define(INTERVALLE_MIN, 2). --define(INTERVALLE_MAX, 5). - - -% N est le nombre d'utilisateur -% M est le nombre de message que chaque utilisateur va poster -start(N, M) -> - Ids = creer_users(N), - lists:map( - fun(Id) -> - timer:sleep(100), - spawn( - fun() -> - {A1, A2, A3} = now(), - random:seed(A1, A2, A3), - loop(Id, M) - end - ) - end, - Ids - ). - -stop(Pids) -> - lists:foreach(fun(Pid) -> exit(Pid, kill) end, Pids). - -% des trucs qui trainent -bench_get_messages() -> - T = [ - {page,"chat"}, - {cookie,"5G84A5CJXMCPEHNI8T5A9"}, - {message_count,40}, - {last_message_id,0}, - {main_page,1}, - {troll_id,0}, - {conversations,{array,[]}} - ], - moyenne_temps(euphorik_protocole, wait_event, [T], 20). -bench_get_messages_avec_2_conversations() -> - T = [ - {page,"chat"}, - {cookie,"5G84A5CJXMCPEHNI8T5A9"}, - {message_count,40}, - {last_message_id,0}, - {main_page,1}, - {troll_id,0}, - {conversations,{array, [ - {struct, [ - {root, 921}, - {page,1}, - {last_message_id,0} - ]}, - {struct, [ - {root, 772}, - {page, 1}, - {last_message_id, 0} - ]} - ]}} - ], - moyenne_temps(euphorik_protocole, wait_event, [T], 20). -moyenne_temps(Module, Fun, Args, N) -> - moyenne_temps(Module, Fun, Args, N, N, 0). -moyenne_temps(_, _, _, 0, Total, Temps_acc) -> - Temps_acc / Total; -moyenne_temps(Module, Fun, Args, N, Total, Temps_acc) -> - {Temps, _} = timer:tc(Module, Fun, Args), - moyenne_temps(Module, Fun, Args, N - 1, Total, Temps_acc + Temps). - - -% Crée N user avec des noms aléatoires et renvoie la liste des id. -creer_users(N) -> - creer_users(N, []). -creer_users(0, Ids) -> lists:map(fun(#user{id = Id}) -> Id end, Ids); -creer_users(N, Ids) -> - creer_users(N - 1, [euphorik_bd:nouveau_user(mot_rand(random:uniform(4) + 4), "", "", #profile{}) | Ids ]). - - -% crée un message aléatoire et le renvoie -message_rand() -> - lists:flatten(message_rand(random:uniform(10), [])). -message_rand(0, Mots) -> Mots; -message_rand(N, Mots) -> - message_rand(N - 1, [mot_rand(random:uniform(2) + 5), $ | Mots]). - - -% Renvoie une succession de lettre aléatoire -mot_rand(L) -> - mot_rand(L, []). -mot_rand(0, Mot) -> Mot; -mot_rand(L, Mot) -> - mot_rand(L - 1, [random:uniform($z - $a + 1) + $a - 1 | Mot]). - - -% Tire au hasard de 0 à 3 messages sur les 10 derniers postés, renvoie une liste de int() -% répartition : -% 0 : 0.1 -% 1 : 0.95 -% 2 : 0.04 -% 3 : 0.01 -messages_id_rand() -> - R = random:uniform(), - if R =< 0.1 -> - []; - true -> - Messages = lists:map(fun(#minichat{id = Id}) -> Id end, euphorik_bd:messages(8)), - if - R > 0.1 andalso R =< 0.95 -> - tire_element_rand(1, Messages); - R > 0.95 andalso R =< 0.99 -> - tire_element_rand(2, Messages); - true -> - tire_element_rand(3, Messages) - end - end. - - -% tire N element distinct parmis la liste L proposée -tire_element_rand(N, L) when N =< length(L) -> - tire_element_rand(N, L, []); -tire_element_rand(_, _) -> - []. -tire_element_rand(0, _, Elements) -> Elements; -tire_element_rand(N, L, Elements) -> - E = lists:nth(random:uniform(length(L)), L), - E_se_trouve_dans_Elements = lists:any(fun(E2) -> E2 =:= E end, Elements), - if E_se_trouve_dans_Elements -> % si E a déjà été tiré on recommence sans rien changer - tire_element_rand(N, L, Elements); - true -> - tire_element_rand(N-1, L, [E | Elements]) - end. - -loop(User_id, 0) -> - io:format("~p a fini~n", [User_id]); -loop(User_id, M) -> - % attend un temp aléatoire compris entre INTERVALLE_MIN sec et INTERVALLE_MAX sec - timer:sleep(1000 * (random:uniform(?INTERVALLE_MAX - ?INTERVALLE_MIN + 1) + ?INTERVALLE_MIN - 1)), - % poste un message aléatoire par une personne aléatoire répondant à des messages aléatoires - {Message, Repond_a} = {message_rand(), messages_id_rand()}, - % io:format("~p poste ~p et repond a ~w~n", [User_id, Message, Repond_a]), - case euphorik_bd:nouveau_message(Message, User_id, Repond_a) of - {erreur, E} -> - io:format("~p : erreur : ~p~n", [User_id, E]), - loop(User_id, M); - _ -> - loop(User_id, M - 1) - end. - - -% Permet de tester la vitesse d'écriture en fonction de la -% taille de la BD -% voir : http://erlang.org/pipermail/erlang-questions/2008-October/038697.html -bench_write_minichat(Filename) -> - Times = bench_write_minichat(1, []), - {ok, File} = file:open(Filename, [write]), - lists:foreach( - fun({Id, Time}) -> - io:format(File, "~w ~w~n", [Id, Time]) - end, - Times - ), - file:close(File). -bench_write_minichat(100000, Temps) -> Temps; -bench_write_minichat(N, Temps) -> - {T, _} = timer:tc(mnesia, transaction, [fun() -> - Id = mnesia:dirty_update_counter(counter, minichat, 1), - mnesia:write(#minichat{ - id = Id, - auteur_id = random:uniform(10000), - date = now(), - pseudo = "Test", - contenu = "Blabla blabla bla.", - racine_id = random:uniform(10000) - }) - end]), - bench_write_minichat(N + 1, if N rem 500 =:= 0 -> [{N, T} | Temps]; true -> Temps end). - - - - \ No newline at end of file +-define(INTERVALLE_MAX, 5). + + +% N est le nombre d'utilisateur +% M est le nombre de message que chaque utilisateur va poster +start(N, M) -> + Ids = creer_users(N), + lists:map( + fun(Id) -> + timer:sleep(100), + spawn( + fun() -> + {A1, A2, A3} = now(), + random:seed(A1, A2, A3), + loop(Id, M) + end + ) + end, + Ids + ). + +stop(Pids) -> + lists:foreach(fun(Pid) -> exit(Pid, kill) end, Pids). + +% des trucs qui trainent +bench_get_messages() -> + T = [ + {page,"chat"}, + {cookie,"5G84A5CJXMCPEHNI8T5A9"}, + {message_count,40}, + {last_message_id,0}, + {main_page,1}, + {conversations,{array,[]}} + ], + moyenne_temps(euphorik_protocole, wait_event, [T], 20). +bench_get_messages_avec_2_conversations() -> + T = [ + {page,"chat"}, + {cookie,"5G84A5CJXMCPEHNI8T5A9"}, + {message_count,40}, + {last_message_id,0}, + {main_page,1}, + {conversations,{array, [ + {struct, [ + {root, 921}, + {page,1}, + {last_message_id,0} + ]}, + {struct, [ + {root, 772}, + {page, 1}, + {last_message_id, 0} + ]} + ]}} + ], + moyenne_temps(euphorik_protocole, wait_event, [T], 20). +moyenne_temps(Module, Fun, Args, N) -> + moyenne_temps(Module, Fun, Args, N, N, 0). +moyenne_temps(_, _, _, 0, Total, Temps_acc) -> + Temps_acc / Total; +moyenne_temps(Module, Fun, Args, N, Total, Temps_acc) -> + {Temps, _} = timer:tc(Module, Fun, Args), + moyenne_temps(Module, Fun, Args, N - 1, Total, Temps_acc + Temps). + + +% Crée N user avec des noms aléatoires et renvoie la liste des id. +creer_users(N) -> + creer_users(N, []). +creer_users(0, Ids) -> lists:map(fun(#user{id = Id}) -> Id end, Ids); +creer_users(N, Ids) -> + creer_users(N - 1, [euphorik_bd:nouveau_user(mot_rand(random:uniform(4) + 4), "", "", #profile{}) | Ids ]). + + +% crée un message aléatoire et le renvoie +message_rand() -> + lists:flatten(message_rand(random:uniform(10), [])). +message_rand(0, Mots) -> Mots; +message_rand(N, Mots) -> + message_rand(N - 1, [mot_rand(random:uniform(2) + 5), $ | Mots]). + + +% Renvoie une succession de lettre aléatoire +mot_rand(L) -> + mot_rand(L, []). +mot_rand(0, Mot) -> Mot; +mot_rand(L, Mot) -> + mot_rand(L - 1, [random:uniform($z - $a + 1) + $a - 1 | Mot]). + + +% Tire au hasard de 0 à 3 messages sur les 10 derniers postés, renvoie une liste de int() +% répartition : +% 0 : 0.1 +% 1 : 0.95 +% 2 : 0.04 +% 3 : 0.01 +messages_id_rand() -> + R = random:uniform(), + if R =< 0.1 -> + []; + true -> + Messages = lists:map(fun(#minichat{id = Id}) -> Id end, euphorik_bd:messages(8)), + if + R > 0.1 andalso R =< 0.95 -> + tire_element_rand(1, Messages); + R > 0.95 andalso R =< 0.99 -> + tire_element_rand(2, Messages); + true -> + tire_element_rand(3, Messages) + end + end. + + +% tire N element distinct parmis la liste L proposée +tire_element_rand(N, L) when N =< length(L) -> + tire_element_rand(N, L, []); +tire_element_rand(_, _) -> + []. +tire_element_rand(0, _, Elements) -> Elements; +tire_element_rand(N, L, Elements) -> + E = lists:nth(random:uniform(length(L)), L), + E_se_trouve_dans_Elements = lists:any(fun(E2) -> E2 =:= E end, Elements), + if E_se_trouve_dans_Elements -> % si E a déjà été tiré on recommence sans rien changer + tire_element_rand(N, L, Elements); + true -> + tire_element_rand(N-1, L, [E | Elements]) + end. + +loop(User_id, 0) -> + io:format("~p a fini~n", [User_id]); +loop(User_id, M) -> + % attend un temp aléatoire compris entre INTERVALLE_MIN sec et INTERVALLE_MAX sec + timer:sleep(1000 * (random:uniform(?INTERVALLE_MAX - ?INTERVALLE_MIN + 1) + ?INTERVALLE_MIN - 1)), + % poste un message aléatoire par une personne aléatoire répondant à des messages aléatoires + {Message, Repond_a} = {message_rand(), messages_id_rand()}, + % io:format("~p poste ~p et repond a ~w~n", [User_id, Message, Repond_a]), + case euphorik_bd:nouveau_message(Message, User_id, Repond_a) of + {erreur, E} -> + io:format("~p : erreur : ~p~n", [User_id, E]), + loop(User_id, M); + _ -> + loop(User_id, M - 1) + end. + + +% Permet de tester la vitesse d'écriture en fonction de la +% taille de la BD +% voir : http://erlang.org/pipermail/erlang-questions/2008-October/038697.html +bench_write_minichat(Filename) -> + Times = bench_write_minichat(1, []), + {ok, File} = file:open(Filename, [write]), + lists:foreach( + fun({Id, Time}) -> + io:format(File, "~w ~w~n", [Id, Time]) + end, + Times + ), + file:close(File). +bench_write_minichat(100000, Temps) -> Temps; +bench_write_minichat(N, Temps) -> + {T, _} = timer:tc(mnesia, transaction, [fun() -> + Id = mnesia:dirty_update_counter(counter, minichat, 1), + mnesia:write(#minichat{ + id = Id, + auteur_id = random:uniform(10000), + date = now(), + pseudo = "Test", + contenu = "Blabla blabla bla.", + racine_id = random:uniform(10000) + }) + end]), + bench_write_minichat(N + 1, if N rem 500 =:= 0 -> [{N, T} | Temps]; true -> Temps end). + + + diff --git a/modules/include/euphorik_bd.hrl b/modules/include/euphorik_bd.hrl index 4838b30..de1c5e4 100755 --- a/modules/include/euphorik_bd.hrl +++ b/modules/include/euphorik_bd.hrl @@ -1,124 +1,123 @@ -% 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 . -% -% @author GBurri - - -% Version de la BD --define(VERSION_BD, 3). --define(TABLES, [counter, proprietes, minichat, reponse_minichat, user, ip_table, troll]). - - -% Pour générer des id --record(counter, - { - key, - value - }). - - -% Mémorse toutes les propriétés, entre autre la version des données --record(proprietes, - { - nom, - valeur - }). - - -% Contient tous les textes que peut envoyer le serveur vers client. --record(texte, - { - id, - fr - }). - - -% décrit un enregistrement d'un message --record(minichat, - { - id, % integer - auteur_id, % -> #user.id - date, % erlang:now() - pseudo, % chaine de caractère - contenu, % chaine de caractère - racine_id = undefined, % la racine, par défaut correspond à l'id du message - status = normal % can be equal to normal, censored or deleted - }). - - -% type bag -% 'repondant' repond à 'cible' --record(reponse_minichat, - { - repondant, % -> #minichat.id - cible % -> #minichat.id - }). - - --record(profile, % attention : pas une table ! - { - pseudo = [], % string() - email = [], % string() - css = [], % string() - nick_format = nick, %atom(), peut valoir 'nick', 'login' ou 'nick_login' - view_times = true, - view_tooltips = true, - ostentatious_master = light, % peut valoir invisible, light ou heavy. seulement pour ek_master - chat_order = reverse, % peut valoir chrono ou reverse - conversations = [] % [{integer(), bool}], la liste des messages correspondant au conversation {racine, reduite?} +% 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 . +% +% @author GBurri + + +% Version de la BD +-define(VERSION_BD, 3). +-define(TABLES, [counter, proprietes, minichat, reponse_minichat, user, ip_table, troll]). + + +% Pour générer des id +-record(counter, + { + key, + value }). - - --record(user, - { - id, - cookie, % string() - login = [], % string() - password = [], % string() (md5) - profile = #profile{}, - date_creation, % erlang:now() - date_derniere_connexion, % erlang:now(), est mis à jour lors de n'importe quelle activitée (envoie de message par exemple) - indice_flood = 0, % integer() est incrémenté lorsque l'utilisateur envoie trop rapidement des messages. - ek_master = false, - last_ip = undefined % integer(), undefined si inconnu - }). - - -% identificateur : (ip) --record(ip_table, - { - ip, % {integer(), integer(), integer(), integer()} - ban = undefined, % la date du dernier bannissement - ban_duration = 0, % le temps de ban en minute - nb_try_register = 0, - nb_try_login = 0, % pour l'instant pas utilisé - date_last_try_register, - date_last_try_login % pour l'instant pas utilisé - }). - - --record(troll, - { - id, + + +% Mémorse toutes les propriétés, entre autre la version des données +-record(proprietes, + { + nom, + valeur + }). + + +% Contient tous les textes que peut envoyer le serveur vers client. +-record(texte, + { + id, + fr + }). + + +% décrit un enregistrement d'un message +-record(minichat, + { + id, % integer + auteur_id, % -> #user.id + date, % erlang:now() + pseudo, % chaine de caractère + contenu, % chaine de caractère + racine_id = undefined, % la racine, par défaut correspond à l'id du message + status = normal % can be equal to normal, censored or deleted + }). + + +% type bag +% 'repondant' repond à 'cible' +-record(reponse_minichat, + { + repondant, % -> #minichat.id + cible % -> #minichat.id + }). + + +-record(profile, % attention : pas une table ! + { + pseudo = [], % string() + email = [], % string() + css = [], % string() + nick_format = nick, %atom(), peut valoir 'nick', 'login' ou 'nick_login' + view_times = true, + view_tooltips = true, + ostentatious_master = light, % peut valoir invisible, light ou heavy. seulement pour ek_master + chat_order = reverse, % peut valoir chrono ou reverse + conversations = [] % [{integer(), bool}], la liste des messages correspondant au conversation {racine, reduite?} + }). + + +-record(user, + { + id, + cookie, % string() + login = [], % string() + password = [], % string() (md5) + profile = #profile{}, + date_creation, % erlang:now() + date_derniere_connexion, % erlang:now(), est mis à jour lors de n'importe quelle activitée (envoie de message par exemple) + indice_flood = 0, % integer() est incrémenté lorsque l'utilisateur envoie trop rapidement des messages. + ek_master = false, + last_ip = undefined % integer(), undefined si inconnu + }). + + +% identificateur : (ip) +-record(ip_table, + { + ip, % {integer(), integer(), integer(), integer()} + ban = undefined, % la date du dernier bannissement + ban_duration = 0, % le temps de ban en minute + nb_try_register = 0, + nb_try_login = 0, % pour l'instant pas utilisé + date_last_try_register, + date_last_try_login % pour l'instant pas utilisé + }). + + +-record(troll, + { + id, id_user, - id_minichat = undefined, % l'id du message associé - date_create, % erlang:now() - date_post = undefined, % date à laquelle le troll est affiché sur la page principale. undefined initialement puis erlang:now() quand affiché - content % chaine de caractère - }). - \ No newline at end of file + id_minichat = undefined, % l'id du message associé + date_create, % erlang:now() + date_post = undefined, % date à laquelle le troll est affiché sur la page principale. undefined initialement puis erlang:now() quand affiché + content % chaine de caractère + }). diff --git a/modules/include/euphorik_defines.hrl b/modules/include/euphorik_defines.hrl index 8d22486..37fa542 100755 --- a/modules/include/euphorik_defines.hrl +++ b/modules/include/euphorik_defines.hrl @@ -27,7 +27,7 @@ -define(DUREE_SPAM, 1000). % ms % Lorsque l'indice de spam d'un utilisateur atteind cette valeur alors il ne peut plus poster pendant un moment -define(INDICE_SPAM_MAX, 6). -% Un utilisateur ayant trop spamé est bloqué pendant ce temps +% Un utilisateur ayant trop spamé est bloqué pendant ce temps -define(DUREE_BLOCAGE_SPAM, 20000). % ms @@ -36,7 +36,7 @@ % le temps entre deux tentatives de register pour compter un flood -define(TEMPS_FLOOD_REGISTER, 1500). % 1500 ms % après 5 flood l'ip fautive est considérée comme bannie --define(NB_MAX_FLOOD_REGISTER, 5). +-define(NB_MAX_FLOOD_REGISTER, 5). % le nombre max de troll qui peuvent être en attente d'être posté (tous les utilisateurs réunis) diff --git a/tools/start_tv.erl b/tools/start_tv.erl index 8307a0e..d73d52d 100755 --- a/tools/start_tv.erl +++ b/tools/start_tv.erl @@ -3,7 +3,7 @@ main(_) -> net_kernel:start([tv, shortnames]), - Yaws = yaws_dev@flynux, + Yaws = yaws_dev@X220, io:format("Connexion à Yaws : ~p~n", [net_adm:ping(Yaws)]), mnesia:start(), mnesia:change_config(extra_db_nodes, nodes()), diff --git a/tools/tools.erl b/tools/tools.erl old mode 100644 new mode 100755 index ffab671..d5c24bf --- a/tools/tools.erl +++ b/tools/tools.erl @@ -1,5 +1,5 @@ -#!/usr/bin/env escript -% coding: utf-8 +#!/usr/bin/env escript +% coding: utf-8 % Copyright 2008 Grégory Burri % % This file is part of Euphorik. @@ -15,11 +15,11 @@ % 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 . +% along with Euphorik. If not, see . -% This file replace the old one 'tools.rb' written in a crapy language ;) -% TODO : -% - création de unit tests (voir eunit) et validation avant la mise en prod +% This file replace the old one 'tools.rb' written in a crapy language ;) +% TODO : +% - création de unit tests (voir eunit) et validation avant la mise en prod main(Args) -> @@ -44,11 +44,11 @@ main(Args) -> % A simple fonction to log message. log(Str) -> io:format("===== ~s =====~n", [Str]). - + % Execute an OS commande and print the result to stdout. cmd(Commande_str) -> - cmd(Commande_str, []). + cmd(Commande_str, []). cmd(Commande_str, Params) -> io:format("~s~n", [os:cmd(lists:flatten(io_lib:format(Commande_str, Params)))]). @@ -94,47 +94,47 @@ in_preprod(Uri, data_path) -> start_server(), update_server(). - + % Compile the Erlang modules. compile_server_part(Uri) -> todo. - + % Create the 'var' folder if it doesn't exist. make_var_directory(Uri) -> todo. - - + + % Copy files from developpement env to production server. copy_files(Uri) -> copy_static_part(Uri), copy_packed_js(Uri). - - + + % Copy all static files like modules, styles, pages, etc. copy_static_part(Uri) -> %~ creer_rep('modules') %~ system("rsync -r --exclude 'euphorik_test.beam' modules/ebin #{@uri}:#{@rep}/modules") %~ system("rsync -r modules/include #{@uri}:#{@rep}/modules") todo. - + % Minify and pack JavaScript in one file then copy it to ther server. copy_packed_js(Uri) -> todo. - - + + % Define the rights for the copied folder et files. define_files_rights(Uri) -> - todo. - - + todo. + + % Start the server if it not already started (in preproduction case only). start_server() -> todo. - - -% Run a erlang script to + + +% Run a erlang script to update_server() -> todo. - + diff --git a/tools/tools.old.rb b/tools/tools.old.rb old mode 100644 new mode 100755 -- 2.43.0