REPORT de la branche 1.1 (459->476)
authorGreg Burri <greg.burri@gmail.com>
Mon, 6 Oct 2008 08:21:00 +0000 (08:21 +0000)
committerGreg Burri <greg.burri@gmail.com>
Mon, 6 Oct 2008 08:21:00 +0000 (08:21 +0000)
23 files changed:
js/betterjs.js
js/client.js
js/formateur.js
js/fragment.js
js/pageMinichat/commandes.js
js/pageMinichat/conversation.js
js/pageMinichat/conversations.js
js/pageMinichat/pageMinichat.js
js/util.js
modules/Makefile
modules/erl/euphorik_bd.erl
modules/erl/euphorik_bd_admin.erl
modules/erl/euphorik_minichat_conversation.erl
modules/erl/euphorik_protocole.erl
modules/erl/euphorik_test.erl
modules/include/euphorik_bd.hrl
styles/1/euphorik.css
styles/1/pageMinichat.css
styles/2/euphorik.css
styles/2/pageMinichat.css
tools/mise_en_prod.erl
tools/start_yaws.sh
tools/tools.rb

index 69788d8..d982030 100644 (file)
@@ -1,3 +1,4 @@
+// coding: utf-8
 // tout un tas d'améliorations de JavaScript ;)
 
 
index 49f7749..c378075 100644 (file)
@@ -26,7 +26,7 @@ euphorik.Client = function(util, communication) {
    this.communication = communication;\r
    \r
    this.cookie = null;\r
-   this.regexCookie = /^cookie=([^;]*)/;\r
+   this.regexCookie = /cookie=([^;]*)/;\r
    \r
    // données personnels\r
    this.resetDonneesPersonnelles();\r
@@ -295,7 +295,7 @@ euphorik.Client.prototype.getJSONEnregistrement = function(login, password) {
 };\r
 \r
 /**\r
-  * Connexion. Réalisée de manière synchrone.\r
+  * Connexion. Réalisé de manière synchrone.\r
   */\r
 euphorik.Client.prototype.connexion = function(action, messageJson) {\r
    var thisClient = this;\r
@@ -369,7 +369,7 @@ euphorik.Client.prototype.flush = function(async) {
    if (!this.authentifie()) {\r
       return false;\r
    }\r
-\r
+   \r
    var thisClient = this;\r
    var ok = true;\r
    \r
index 4b74d44..eba6c5a 100644 (file)
@@ -28,7 +28,7 @@ euphorik.Formateur = function() {
    \r
    this.regexUrl = new RegExp("(?:(?:" + this.protocoles + ")://|www\\.)[^ ]*", "gi");\r
    this.regexImg = new RegExp("^.*?\\.(gif|jpg|png|jpeg|bmp|tiff)$", "i");\r
-   this.regexDomaine = new RegExp("^(?:(?:" + this.protocoles + ")://|www\\.).*?([^/.]+\\.[^/.]+)(?:$|/).*$", "i");\r
+   this.regexDomaine = new RegExp("^(?:(?:" + this.protocoles + ")://)(.*?)(?:$|/).*$", "i");\r
    this.regexTestProtocoleExiste = new RegExp("^(?:" + this.protocoles + ")://.*$", "i");\r
    this.regexNomProtocole = new RegExp("^(.*?)://");\r
 };\r
@@ -124,7 +124,7 @@ euphorik.Formateur.prototype.traiterWikiSyntaxe = function(m) {
 \r
 /**\r
   * Renvoie une version courte de l'url.\r
-  * par exemple : http://en.wikipedia.org/wiki/Yakov_Smirnoff devient wikipedia.org\r
+  * par exemple : http://en.wikipedia.org/wiki/Yakov_Smirnoff devient en.wikipedia.org\r
   */\r
 euphorik.Formateur.prototype.getShort = function(url) {\r
    var estUneImage = false;\r
index ef087f4..4ffbf8f 100644 (file)
   */
 Fragment = function() {
    var thisFragment = this;
+   // remplacement des codes de type %22 (en hexa)
+   var replaceHtmlCode = function(str) {
+      return str.replace(/%(\d\d)/g, function(text, code) {
+         return String.fromCharCode(parseInt(code, 16));
+      });
+   };
    this.fragments = {};
    if (!window.location.hash) {
        return;
    }
    try {
-       var fragmentsStr = window.location.hash.slice(1).split(";");
+       var fragmentsStr = replaceHtmlCode(window.location.hash.slice(1)).split(";");
        fragmentsStr.each(function(i, tuple) {
           tuple = tuple.split("=");
           thisFragment.fragments[tuple[0]] = JSON.parse(tuple[1]);
index f49ce9a..014b9bb 100644 (file)
   *  /nick <nouveau nick>
   *  Modifie le pseudo courant
   */
-euphorik.Commandes = function(client) {
+euphorik.Commandes = function(client, pageMinichat, util, formateur) {
+   var thisCommandes = this;
+
    this.client = client;
+   this.pageMinichat = pageMinichat;
+   this.util = util;
+   this.formateur = formateur;
+   
+   // construction du texte d'aide (liste des commandes) de manière statique
+   this.texteAide = "<div id=\"aideCommandes\"><h1>Commandes</h1><ul>";
+   objectEach(
+      euphorik.Commandes.liste,
+      function(nom, commande) {
+         thisCommandes.texteAide += "<li><span class=\"usage\">" + thisCommandes.formateur.traitementComplet(commande.usage) + "</span> : " + thisCommandes.formateur.traitementComplet(commande.description) + "</li>";
+      }
+   );
+   this.texteAide += "</ul></div>";
 };
 
 euphorik.Commandes.statut = {ok : 0, pas_une_commande : 1, erreur_commande : 2};
 
 euphorik.Commandes.liste = {
    "nick" : {
+      description : "Change le pseudo courant",
       usage : "/nick <nouveau pseudo>",
       exec : function(args, client) {
          
@@ -47,7 +63,15 @@ euphorik.Commandes.liste = {
    
          return [euphorik.Commandes.statut.ok, ''];
       }
-   }      
+   },
+   "cpf" : {
+      description : "Envoie le message \"C'est pas faux\"",
+      usage : "/cpf",
+      exec : function(args, client, pageMinichat) {
+         pageMinichat.envoyerMessage("C'est pas faux");
+         return [euphorik.Commandes.statut.ok, ''];
+      }
+   }
 };
 
 /**
@@ -68,9 +92,20 @@ euphorik.Commandes.prototype.exec = function(chaine) {
    if (nomCommande === "") {
       return [euphorik.Commandes.statut.erreur_commande, 'La commande est vide'];
    }
+   // commandes spéciales pour afficher l'aide : "?", "h", "help", "aide"
+   if (nomCommande === "?" || nomCommande === "h" || nomCommande === "help" || nomCommande === "aide") {
+      this.util.messageDialogue(
+         this.texteAide,
+         euphorik.Util.messageType.informatif,
+         {"fermer" : function(){}},
+         false,
+         -1
+      );
+      return [euphorik.Commandes.statut.ok, ''];
+   }
    
    if (euphorik.Commandes.liste.hasOwnProperty(nomCommande)) {
-      return euphorik.Commandes.liste[nomCommande].exec(args, this.client);
+      return euphorik.Commandes.liste[nomCommande].exec(args, this.client, this.pageMinichat);
    }
    
    return [euphorik.Commandes.statut.erreur_commande, 'La commande /' + nomCommande + ' est inconnue'];
index 2810b1b..acd7d67 100644 (file)
@@ -244,15 +244,22 @@ euphorik.Conversation.prototype.flush = function() {
 \r
    var messagePair = (this.idDernierMessageAffiche === 0 ? true :\r
       ($("#" + this.getId() + " .messages div:" + (reverse ? "first" : "last")).attr("class").search("messagePair") === -1)\r
-   );\r
+   );
+   
+   // 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);
+         }
+      });
+   };\r
       \r
    // construction de l'XHTML des messages\r
-   var XHTML = "";\r
-   this.messages.each(function(i, mess) {\r
-      if (mess.id > thisConversation.idDernierMessageAffiche) {\r
-         XHTML += mess.XHTML(messagePair, thisConversation.getId());\r
-         messagePair = !messagePair;\r
-      }\r
+   var XHTML = "";
+   pourChaqueNouveauMessage(function(mess) {\r
+      XHTML += mess.XHTML(messagePair, thisConversation.getId());\r
+      messagePair = !messagePair;\r
    });\r
    \r
    var DOM = $(XHTML);       \r
@@ -274,11 +281,25 @@ euphorik.Conversation.prototype.flush = function() {
       } else {\r
          $("#" + this.getId() + " .messages .message").slice(0, nbMessagesAffiche - this.nbMessageMax).remove();\r
       }\r
-   }\r
+   }
+  
+   // 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")
+            }
+         });
+      }
+   });
    \r
    if (this.messages.length > 0) {\r
       this.idDernierMessageAffiche = this.messages[this.messages.length-1].id;\r
-   }\r
+   }
   \r
    // met à jour la racine de la conversation\r
    this.majRacine();\r
@@ -326,7 +347,7 @@ euphorik.Conversation.prototype.attacherEventsSurMessage = function(element) {
       // donne le focus à la ligne de saisie\r
       $("form input.message").focus();\r
    }).hover(function() { // affiche les outils liées au message\r
-      var top = $(this).offset().top\r
+      var top = $(this).offset().top;\r
       var left = $(this).offset().left + $(this).outerWidth() - thisConversation.util.outilsMessage.outerWidth();\r
       $(".extraire", thisConversation.util.outilsMessage).unbind();\r
       $(".extraireCompletement", thisConversation.util.outilsMessage).unbind();\r
@@ -336,7 +357,7 @@ euphorik.Conversation.prototype.attacherEventsSurMessage = function(element) {
       thisConversation.util.infoBulle("Extraction de la conversation complète", $(".extraireCompletement", thisConversation.util.outilsMessage));\r
       thisConversation.util.outilsMessage.css("top", top).css("left", left).prependTo(this).show();\r
    }, function() {\r
-      thisConversation.util.outilsMessage.hide()\r
+      thisConversation.util.outilsMessage.hide();\r
    });\r
 \r
    // mise en évidence de la conversation\r
index b99e092..94236c1 100644 (file)
@@ -63,11 +63,11 @@ euphorik.Conversations.prototype.toggleMessageRepond = function(mess) {
 euphorik.Conversations.prototype.mettreAJourFragment = function() {\r
    conv = [];\r
    for(var i = 1; i < this.conversations.length; i++) {\r
-       conv.push(this.conversations[i].racine.id)\r
+       conv.push(this.conversations[i].racine.id);\r
    }\r
    this.fragment.setVal("conv", conv);\r
    this.fragment.write();\r
-}\r
+};\r
 \r
 /**\r
   * Enlève tous les messages auquel l'utilisateur souhaite répondre.\r
@@ -195,7 +195,7 @@ euphorik.Conversations.prototype.getJSONrafraichirMessages = function() {
 };\r
 \r
 euphorik.Conversations.prototype.getJSONConversations = function() {\r
-   var thisConversations = this\r
+   var thisConversations = this;\r
    var clientConv = [];\r
    \r
    this.client.conversations.each(function(i, conv) {\r
index f11dea4..b71efea 100755 (executable)
@@ -25,7 +25,7 @@ euphorik.PageMinichat = function(client, formateur, util, communication) {
    this.formateur = formateur;
    this.util = util;
    this.communication = communication;
-   this.commandes = new euphorik.Commandes(this.client);
+   this.commandes = new euphorik.Commandes(this.client, this, this.util, this.formateur);
    
    // permet d'éviter d'envoyer plusieurs messages simultanément en pressant
    // rapidement sur "enter" par exemple
@@ -163,7 +163,7 @@ euphorik.PageMinichat.prototype.charger = function() {
          var retCommandes = thisPage.commandes.exec(message);         
          switch (retCommandes[0]) {
             case euphorik.Commandes.statut.pas_une_commande :
-               thisPage.envoyerMessage($("form#posterMessage input.pseudo").val(), message);
+               thisPage.envoyerMessage(message);
                break;
             case euphorik.Commandes.statut.erreur_commande :
                thisPage.util.messageDialogue(retCommandes[1], euphorik.Util.messageType.erreur);
@@ -213,18 +213,23 @@ euphorik.PageMinichat.prototype.chargerConversationsFragment = function() {
    } catch(e) {
       ;; console.log(e)
    }
-}
+};
   
 euphorik.PageMinichat.prototype.decharger = function() {
    this.conversations.comet.stopAttenteCourante();
    
    $("body #smiles").remove();
    
-    this.fragment.delVal("conv")
+    this.fragment.delVal("conv");
 };
 
-euphorik.PageMinichat.prototype.envoyerMessage = function(pseudo, message) {   
-   var thisPageMinichat = this;
+/**
+  * Envoie un nouve message donné, le pseudo utilisé est celui se trouvant
+  * dans la zone de saisie (form#posterMessage input.pseudo).
+  */
+euphorik.PageMinichat.prototype.envoyerMessage = function(message) {   
+   var thisPageMinichat = this;   
+   var pseudo = $("form#posterMessage input.pseudo").val();
 
    // (un pseudo vide est autorisé)
    pseudo = this.formateur.filtrerInputPseudo(pseudo);
@@ -239,6 +244,8 @@ euphorik.PageMinichat.prototype.envoyerMessage = function(pseudo, message) {
       this.util.messageDialogue("Le message est vide");
       return;
    }
+   
+   this.client.pseudo = pseudo;
 
    if (!this.client.authentifie()) {
       if (!this.client.enregistrement()) {
@@ -247,8 +254,6 @@ euphorik.PageMinichat.prototype.envoyerMessage = function(pseudo, message) {
       }
    }
       
-   this.client.pseudo = pseudo;
-   
    // évite le double post
    if (this.envoieMessageEnCours) {
       this.util.messageDialogue("Message en cours d'envoie...");
@@ -260,17 +265,6 @@ euphorik.PageMinichat.prototype.envoyerMessage = function(pseudo, message) {
       "put_message",
       this.getJSONMessage(pseudo, message),
       function() {
-         // TODO : revoir cette partie
-         // met à jour la classe des messages auquel repond celui ci (c'est un peu de la triche) TODO : ya mieux ?
-         objectEach(thisPageMinichat.conversations.messagesRepond, function(messId) {
-            thisPageMinichat.conversations.conversations.each(function(i, conv) {
-               var mess = conv.messagesParId[messId];
-               if (mess) {
-                  mess.clientARepondu = true;
-                  $("#conversations #" + mess.getId(conv.getId())).addClass("repondu")
-               }
-            });
-         });
          $("form#posterMessage input.message").val("");
          thisPageMinichat.conversations.enleverMessagesRepond(); 
          thisPageMinichat.envoieMessageEnCours = false;
index c859831..f5fca3f 100644 (file)
@@ -43,14 +43,16 @@ euphorik.Util.messageType = {informatif: 0, question: 1, erreur: 2};
   * @param message le message (string)\r
   * @param type voir 'messageType'. par défaut messageType.informatif\r
   * @param les boutons sous la forme d'un objet ou les clefs sont les labels des boutons\r
-  *        et les valeurs les fonctions executées lorsqu'un bouton est activé.\r
-  * @param formate faut-il formaté le message ? true par défaut\r
+  *        et les valeurs les fonctions executées lorsqu'un bouton est activé.
+  *        Lorsqu'un bouton est activé le message se ferme.     \r
+  * @param formate faut-il formaté le message ? true par défaut
+  * @param temps le temps d'affichage du message en seconde, -1 pour une durée infinie\r
   */\r
-euphorik.Util.prototype.messageDialogue = function(message, type, boutons, formate) {\r
+euphorik.Util.prototype.messageDialogue = function(message, type, boutons, formate, temps) {\r
    var thisUtil = this;\r
 \r
    type = type || euphorik.Util.messageType.informatif;\r
-   formate = formate || true;\r
+   formate = formate === undefined ? true : formate;\r
 \r
    if (this.timeoutMessageDialogue) {\r
       clearTimeout(this.timeoutMessageDialogue);\r
@@ -58,7 +60,7 @@ euphorik.Util.prototype.messageDialogue = function(message, type, boutons, forma
       \r
    var fermer = function() { $("#info").slideUp(100); };\r
    fermer();\r
-   \r
+\r
    $("#info .message").html(!thisUtil.formateur || !formate ? message : thisUtil.formateur.traitementComplet(message));\r
    \r
    switch(type) {\r
@@ -72,8 +74,10 @@ euphorik.Util.prototype.messageDialogue = function(message, type, boutons, forma
       $("#info .boutons").append("<div>" + nom + "</div>").find("div:last").click(bouton).click(fermer);\r
    });\r
    \r
-   $("#info").slideDown(200);\r
-   this.timeoutMessageDialogue = setTimeout(fermer, euphorik.conf.tempsAffichageMessageDialogue);\r
+   $("#info").slideDown(200);
+   if (temps !== -1) {\r
+      this.timeoutMessageDialogue = setTimeout(fermer, temps || euphorik.conf.tempsAffichageMessageDialogue);
+   }\r
 };\r
 \r
 euphorik.Util.positionTypeX = {gauche: 0, gaucheRecouvrement: 1, centre: 2, droiteRecouvrement: 3, droite: 4};\r
index 0a77892..859cc56 100755 (executable)
@@ -41,7 +41,7 @@ $(rep_ebin)/euphorik_bd.beam: $(rep_erl)/euphorik_bd.erl $(rep_include)/euphorik
        erlc $(erlc_params)
    
 # Module pour la mise à jour de la BD
-$(rep_ebin)/euphorik_bd_admin.beam: $(rep_erl)/euphorik_bd_admin.erl $(rep_erl)/euphorik_bd.erl $(rep_include)/euphorik_bd.hrl $(rep_include)/euphorik_defines.hrl
+$(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)
 
 # Module permettant l'extraction des conversations du minichat
@@ -61,7 +61,7 @@ $(rep_ebin)/euphorik_protocole.beam: $(rep_erl)/euphorik_protocole.erl $(rep_inc
 #      erlc $(erlc_params)
       
 # Module effectuant periodiquement certaines tâches
-$(rep_ebin)/euphorik_daemon.beam: $(rep_erl)/euphorik_daemon.erl $(rep_include)/euphorik_defines.hrl $(rep_erl)/euphorik_bd_admin.erl
+$(rep_ebin)/euphorik_daemon.beam: $(rep_erl)/euphorik_daemon.erl $(rep_include)/euphorik_defines.hrl
        erlc $(erlc_params)
    
 # Module avec plein de bordel dedant
@@ -69,7 +69,7 @@ $(rep_ebin)/euphorik_common.beam: $(rep_erl)/euphorik_common.erl
        erlc $(erlc_params)
    
 # Module dédié au tests
-$(rep_ebin)/euphorik_test.beam: $(rep_erl)/euphorik_test.erl $(rep_erl)/euphorik_bd.erl $(rep_include)/euphorik_bd.hrl
+$(rep_ebin)/euphorik_test.beam: $(rep_erl)/euphorik_test.erl $(rep_include)/euphorik_bd.hrl
        erlc $(erlc_params)
 
 # Suppression des modules compilés
index 81a36a0..4fecb73 100755 (executable)
@@ -46,7 +46,6 @@
    % messages :\r
    nouveau_message/3,\r
    nouveau_message_sys/1,\r
-   nouveau_message_sys/2,\r
    messages/1,\r
    messages/2,\r
    messages/3,\r
@@ -78,7 +77,6 @@
    troll_by_id/1,\r
    current_troll/0,\r
    elire_troll/0,\r
-   message_id_associe/1,\r
    \r
    % utiles :\r
    resultat_transaction/1\r
@@ -277,13 +275,16 @@ user_by_mess(Id) ->
 nouveau_message(Mess, Auteur_id, Repond_A_ids) ->\r
    % regarde si les id 'Repond_A' existent\r
    F = fun() ->         \r
-      Repond_a = lists:map(\r
-         fun(Repond_a_id) ->\r
-            [M] = mnesia:wread({minichat, Repond_a_id}),\r
-            M\r
-         end,\r
+      Repond_a = lists:foldr(\r
+         fun(Repond_a_id, Acc) ->\r
+            case mnesia:read({minichat, Repond_a_id}) of
+               [M] -> [M | Acc];\r
+               _ -> Acc % le message n'est pas trouvé
+            end\r
+         end,
+         [],\r
          Repond_A_ids\r
-      ),      \r
+      ),\r
       Racine_id = case Repond_a of\r
          [] -> undefined;\r
          [M | _] -> \r
@@ -295,16 +296,16 @@ nouveau_message(Mess, Auteur_id, Repond_A_ids) ->
                _ ->\r
                   {erreur, "Les messages ne font pas partie de la même conversation"}\r
             end\r
-      end,\r
-      case Racine_id of\r
-         {erreur, E} -> {erreur, E};\r
-         _ ->\r
-            % est-ce que l'auteur existe ?\r
-            case mnesia:wread({user, Auteur_id}) of\r
-               [#user{profile = Profile} = Auteur] ->\r
-                  if length(Repond_a) =/= length(Repond_A_ids) ->\r
-                        {erreur, "Un ou plusieurs messages introuvable"};\r
-                     true ->\r
+      end,      
+      if length(Repond_a) =/= length(Repond_A_ids) ->
+            {erreur, "Un ou plusieurs messages introuvable"};
+         true ->\r
+            case Racine_id of\r
+               {erreur, E} -> {erreur, E};\r
+               _ ->\r
+                  % est-ce que l'auteur existe ?\r
+                  case mnesia:wread({user, Auteur_id}) of\r
+                     [#user{profile = Profile} = Auteur] ->\r
                         % comparaison entre la date du dernier poste et maintenant (gestion du flood)\r
                         Now = now(),\r
                         Delta = euphorik_common:delta_date_ms(Auteur#user.date_derniere_connexion, Now),\r
@@ -332,11 +333,11 @@ nouveau_message(Mess, Auteur_id, Repond_A_ids) ->
                                  racine_id = if Racine_id =:= undefined -> Id; true -> Racine_id end\r
                               }),\r
                               Id\r
-                        end\r
-                  end;\r
-               _ ->\r
-                  {erreur, "L'auteur du message est introuvable"}\r
-            end\r
+                        end;\r
+                     _ ->\r
+                        {erreur, "L'auteur du message est introuvable"}\r
+                  end\r
+            end
       end\r
    end,\r
    resultat_transaction(mnesia:transaction(F)).\r
@@ -352,16 +353,11 @@ inserer_reponses(_, []) ->
 % Permet de créer un message système.\r
 % Renvoie l'id du message système\r
 nouveau_message_sys(Mess) ->\r
-   nouveau_message_sys(Mess, undefined).\r
-   \r
-\r
-% Création d'un message système lié à un troll.\r
-nouveau_message_sys(Mess, Troll_id) ->\r
    {ok, #user{profile = Profile}} = user_by_id(0),\r
    resultat_transaction(mnesia:transaction(\r
       fun() ->\r
          Id = nouvel_id(minichat),\r
-         mnesia:write(#minichat{id = Id, auteur_id = 0, date = now(), pseudo = Profile#profile.pseudo, contenu = Mess, troll_id = Troll_id, racine_id = Id}),\r
+         mnesia:write(#minichat{id = Id, auteur_id = 0, date = now(), pseudo = Profile#profile.pseudo, contenu = Mess, racine_id = Id}),\r
          Id\r
       end\r
    )).\r
@@ -372,26 +368,29 @@ messages(N) ->
    messages(N, 1).\r
 \r
 \r
-% Renvoie N messages se trouvant sur la page P\r
+% Renvoie N messages se trouvant sur la page P
+% TODO FIXME : fonction en O(N * P) !\r
 messages(N, P) ->\r
-   F = fun() ->\r
-      C = cursor(\r
-         qlc:keysort(\r
-            #minichat.id, \r
-            q([E#minichat{contenu = contenu_message(E)} || E <- mnesia:table(minichat)]),\r
-            [{order, descending}]\r
-         ),\r
-         [{tmpdir, ?KEY_SORT_TEMP_DIR}]\r
-      ),\r
-      if P > 1 -> qlc:next_answers(C, N * (P - 1));\r
-         true -> ok\r
-      end,\r
-      R = qlc:next_answers(C, N),\r
-      qlc:delete_cursor(C),\r
-      R\r
+   F = fun() ->
+      % % #minichat{contenu = contenu_message(E)}
+      get_tuples_avant(minichat, reculer(minichat, mnesia:last(minichat), N * (P - 1)), N)     \r
    end,\r
-   lists:reverse(resultat_transaction(mnesia:transaction(F))).\r
-\r
+   resultat_transaction(mnesia:transaction(F)).
+   
+get_tuples_avant(Table, Id, N) ->
+   get_tuples_avant(Table, Id, N, []).
+get_tuples_avant(_, '$end_of_table', _, Tuples) -> Tuples;
+get_tuples_avant(_, _, 0, Tuples) ->
+   Tuples;
+get_tuples_avant(Table, Id, N, Tuples) ->
+   [T] = mnesia:read({Table, Id}),
+   get_tuples_avant(Table, mnesia:prev(Table, Id), N - 1, [T | Tuples]).
+
+reculer(_, '$end_of_table' = Fin, _) -> Fin;
+reculer(_, Id, 0) -> Id;
+reculer(Table, Id, N) ->
+   reculer(Table, mnesia:prev(Table, Id), N - 1).
+   \r
 \r
 % Renvoie les messages manquants pour la page P en sachant qu'il y a N message\r
 % par page et que le dernier message que l'on possède est Id\r
@@ -412,9 +411,9 @@ message_by_id(Id) ->
    \r
    \r
 % Renvoie le contenu d'un message donnée en fonction du troll associé, à utiliser à l'intérieur d'une transaction.\r
-% TODO : Cette fonction pourrait être remplacé par un "outer-join", est-ce possible avec qlc ?\r
+% TODO : Cette fonction pourrait être remplacée par un "outer-join", est-ce possible avec qlc ?\r
 contenu_message(E) ->\r
-   case mnesia:read({troll, E#minichat.troll_id}) of\r
+   case mnesia:index_read(troll, E#minichat.id, #troll.id_minichat) of\r
       [] -> E#minichat.contenu;\r
       [T] -> E#minichat.contenu ++ T#troll.content\r
    end.\r
@@ -796,26 +795,13 @@ elire_troll() ->
                plus_de_trolls;\r
             Trolls ->\r
                Troll = lists:nth(random:uniform(length(Trolls)), Trolls),\r
-               Troll2 = Troll#troll{date_post = now()},\r
-               mnesia:write(Troll2),\r
-               nouveau_message_sys("Troll de la semaine : ", Troll2#troll.id)\r
+               Id_message = nouveau_message_sys("Troll de la semaine : "),\r
+               Troll2 = Troll#troll{date_post = now(), id_minichat = Id_message},\r
+               mnesia:write(Troll2)\r
          end\r
       end\r
    ).\r
    \r
-\r
-% Renvoie l'id du message associé au troll dont l'id est donnée.\r
-% Renvoie undefined si il n'y en a pas.\r
-message_id_associe(Troll_id) ->\r
-   resultat_transaction(mnesia:transaction(\r
-      fun() ->\r
-         case e(q([M#minichat.id || M <- mnesia:table(minichat), M#minichat.troll_id =:= Troll_id]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of\r
-            [Id] -> Id;\r
-            _ -> undefined\r
-         end\r
-      end\r
-   )).\r
-\r
    \r
 % Renvoie le résultat d'une transaction (en décomposant le tuple fournit)\r
 resultat_transaction({_, T}) ->\r
index 62ab563..076a570 100644 (file)
@@ -34,8 +34,9 @@
    reset/0,
    update/0,
    
-   backup_text/1,
-   restore_text/1,
+   backup/1,
+   restore/1,
+   change_node_name/4,
    
    toggle_ek_master/1,
    print_users/0,
@@ -58,7 +59,7 @@ version_bd() ->
    
 
 % Instructions pour créer une nouvelle base : 
-% $erl -sname yaws -mnesia dir '"/projets/euphorik/var/BD"'
+% $erl -sname yaws -mnesia dir '"/projets/euphorik/BD"'
 % voir doc/installation.txt
 % >l(euphorik_bd).
 % >euphorik_bd:create().
@@ -84,6 +85,7 @@ create_tables() ->
       {disc_copies, [node()]}
    ]),
    mnesia:create_table(minichat, [
+      {type, ordered_set},
       {attributes, record_info(fields, minichat)},
       {disc_copies, [node()]}
    ]),
@@ -109,12 +111,22 @@ create_tables() ->
    
 % 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(minichat, troll_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, date_post),\r
+   mnesia:add_table_index(troll, id_minichat).
    
    
 % Connexion à la base de données de yaws sur overnux
@@ -203,9 +215,28 @@ update(Version) ->
    
 % Applique une modification de la BD pour passer d'une version à la suivante.
 % crée un backup avant l'application du patch
-% dans var/BD/backups nommé "backup<num>" où <num> et le numéro de la version.
+% dans BD/backups nommé "backup<num>" où <num> 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"\r
+   % et renvoie "styles/1/euphorik.css"\r
+   Transforme_css = fun("css" ++ Reste) ->
+         "styles" ++ Reste;\r
+         (F) -> F\r
+   end,\r
+   Traiter_message = fun(M, Racine) ->\r
+      F = fun(F, M2) -> % seul moyen à ma connaissance pour faire de la récursion dans une lambda fonction, voir : http://www.nabble.com/Auto-generated-functions-td15279499.html\r
+         % met à jour la racine de chaque message qui répond à M\r
+         lists:foreach(\r
+            fun(M3) ->\r
+               mnesia:write(M2#minichat{racine_id = Racine}),\r
+               F(F, M3)\r
+            end,\r
+            euphorik_bd:enfants(M#minichat.id)\r
+         )\r
+      end,\r
+      F(F, M, Racine)\r
+   end,
    mnesia:create_table(texte, [
       {attributes, record_info(fields, texte)},
       {disc_copies, [node()]}
@@ -215,78 +246,133 @@ patch(1) ->
    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, patch1_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}
+            {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),
-      user
+      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),
-      minichat
+      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) -> patch_1_traiter_message(M, M#minichat.id) end, Messages)
+         lists:foreach(fun(M) -> Traiter_message(M, M#minichat.id) end, Messages)
       end
    ) of
       {aborted, Raison} -> {erreur, Raison};
       {atomic, _} -> ok
-   end.
-   
+   end;\r
+% 2 -> 3\r
+patch(2) ->
+   % première étape : changer le type de la table minichat de set à ordered_set
+   % TODO : trouver un meilleur moyen que de passer par un backup
+   backup("tmp"),
+   create(),
+   restore("tmp"),
+   file:delete(dossier_backups() ++ "tmp"),\r
+   mnesia:transform_table(\r
+      troll,\r
+      fun({troll, Id_troll, Id_user, Date_create, Date_post, Content}) ->\r
+         % recherche le message associé s'il existe\r
+         Id_minichat = case e(q([M || M <- mnesia:table(minichat), element(7, M) =:= Id_troll]), [{tmpdir, ?KEY_SORT_TEMP_DIR}]) of\r
+            [M] -> element(2, M);\r
+            _ -> undefined\r
+         end,\r
+         {troll, Id_troll, Id_user, Id_minichat, Date_create, Date_post, Content}\r
+      end,\r
+      record_info(fields, troll)\r
+   ),\r
+   %mnesia:del_table_index(minichat, troll_id),\r
+   mnesia:transform_table(\r
+      minichat,\r
+      fun({minichat, Id, Auteur_id, Date, Pseudo, Contenu, _Troll_id, Racine_id}) ->\r
+         {minichat, Id, Auteur_id, Date, Pseudo, Contenu, Racine_id}\r
+      end,\r
+      record_info(fields, minichat)\r
+   ),\r
+   creer_indexes(). % uniquement pour l'indice sur id_minichat de la table troll\r
 
-% Prend un chemin vers la feuille de style de type "css/1/euphorik.css"
-% et renvoie "styles/1/euphorik.css"
-patch1_transforme_css("css" ++ Reste) ->
-   "styles" ++ Reste;
-patch1_transforme_css(F) ->
-   F.
 
-   
-patch_1_traiter_message(M, Racine) ->
-   % met à jour la racine de chaque message qui répond à M
-   lists:foreach(
-      fun(M2) ->
-         mnesia:write(M2#minichat{racine_id = Racine}),
-         patch_1_traiter_message(M2, Racine)
-      end,
-      euphorik_bd:enfants(M#minichat.id)
-   ).
+% Renvoie le dossier dans lequel les backups sont effectué, ce dossier doit être en écriture.
+dossier_backups() ->
+   mnesia:system_info(directory) ++ "/backups/".
 
-% crée un backup dont le nom est fournit dans le repertoire backups qui se trouve dans le repertoire de la BD.
-%backup(Nom) ->
-%   mnesia:backup(mnesia:system_info(directory) ++ "/backups/" ++ Nom).
-   
 
-% Reviens à une version précédente de la base de données
-% (les données insérées durant les versions plus récentes sont perdues)
-%restore(N) ->
-%   mnesia:restore(fichier_backup(N), [{default_op, recreate_tables}]).
+% 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).
+\r
 
+% 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}]).
+   
 
-% Renvoie le fichier (avec le chemin) correspondant à la version Version, par exemple : "/var/euphorik/var/BD/backups/backup1"
-fichier_backup(Version) -> 
-   mnesia:system_info(directory) ++ "/backups/" ++ if is_integer(Version) -> "backup" ++ integer_to_list(Version); true -> Version end.
+% 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).
 
 
-backup_text(_) -> todo.
-restore_text(File) -> 
-   mnesia:stop(),
-   mnesia:delete_schema([node()]),
-   mnesia:start(),
-   case mnesia:load_textfile(File) of
-      {atomic, ok} ->
-         update(),
-         creer_indexes();
-      Erreur ->
-         Erreur
-   end.
+% 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) ->
@@ -369,4 +455,4 @@ print_user(Id) when is_integer(Id) ->
       _ ->
          {erreur, "Id pas trouvé : " ++ integer_to_list(Id)}
    end.
-   
\ No newline at end of file
+   
index 2f932e2..4d0b3a8 100755 (executable)
@@ -31,8 +31,7 @@
 % Un message est un tuple représentant le message et la liste des id\r
 % des messages auquels il répond\r
 % @type Message() = {#minichat, [int()]}\r
-% \r
-\r
+
 \r
 -module(euphorik_minichat_conversation).\r
 -export([\r
 ]).\r
 -include("../include/euphorik_bd.hrl").\r
 -include("../include/euphorik_defines.hrl").\r
--include_lib("stdlib/include/qlc.hrl").\r
 -import(lists, [reverse/1, any/2, map/2, sublist/3, filter/2]).\r
 -import(euphorik_bd, [resultat_transaction/1]).\r
--import(qlc, [e/2, q/1]).\r
 -import(mnesia, [table/1, transaction/1]).\r
   \r
    \r
@@ -111,26 +108,22 @@ mise_en_forme_conversation(Messages) ->
 % @spec conversations_detailees([{integer(), integer()}], integer(), integer(), integer()) -> [[{integer(), bool()}] | Conversation_detailee()]\r
 conversations_detailees(Racines, N, D, P) ->   \r
    Conversations = map(fun({Racine, P_conv, Dernier}) -> conversation(Racine, N, Dernier, P_conv) end, Racines),\r
-   Conversation_principale = resultat_transaction(transaction(fun() ->\r
-      Curseur = qlc:cursor(\r
-         qlc:sort(q([E#minichat.id || E <- table(minichat)]), [{order, descending}]),\r
-         [{tmpdir, ?KEY_SORT_TEMP_DIR}]\r
-      ),\r
-      {CP, Plus} = conversation_principale(Curseur, Conversations, N, P),\r
-      qlc:delete_cursor(Curseur),\r
+   Conversation_principale = resultat_transaction(transaction(fun() ->
+      Dernier_id = mnesia:last(minichat),\r
+      {CP, Plus} = conversation_principale(Dernier_id, Conversations, N, P),\r
       {[M || M <- CP, M > D], Plus} % filtre en fonction de D\r
    end)),\r
    [Conversation_principale | Conversations].\r
    \r
 \r
-% Construit la conversation principale en fonction d'un curseur C initialement placé sur le dernier message\r
+% Construit la conversation principale en fonction d'un id de message initialement placé sur le dernier message\r
 % et la liste de conversations.\r
 % N est le nombre de messages que l'on souhaite.\r
 % P est le numéro de la page (1, 2, 3...)\r
-% @spec conversation_principale(qlc:QueryCursor(), [Conversation_detailee()], integer(), integer()) -> {[integer()], bool()}\r
-conversation_principale(C, Conversations, N, P) ->\r
+% @spec conversation_principale(integer(), [Conversation_detailee()], integer(), integer()) -> {[integer()], bool()}\r
+conversation_principale(Id, Conversations, N, P) ->\r
    % on prend en message de plus pour savoir s'il y en a plus que ce que l'on désire\r
-   CP = reverse(conversation_principale2(C, lists:flatten(map(fun({C2, _, X, _}) -> C2 -- X end, Conversations)), N + 1, (P - 1) * N)),\r
+   CP = reverse(conversation_principale2(Id, lists:flatten(map(fun({C2, _, X, _}) -> C2 -- X end, Conversations)), N + 1, (P - 1) * N)),\r
    Plus = length(CP) =:= N + 1,\r
    {\r
       if Plus ->\r
@@ -143,25 +136,25 @@ conversation_principale(C, Conversations, N, P) ->
    }.\r
       \r
       \r
-% C est le curseur (voir ci dessus)\r
+% Id est l'id d'un message, voir ce dessus\r
 % 'Messages' sont les messages que l'on doit enlever de la conversation\r
 % S est le nombre de messages qu'il faut sauter.\r
-% @spec conversation_principale2(qlc:QueryCursor(), [integer()], integer(), integer()) -> [integer()]\r
+% @spec conversation_principale2(integer(), [integer()], integer(), integer()) -> [integer()]\r
 conversation_principale2(_, _, 0, _) ->\r
+   [];
+conversation_principale2('$end_of_table', _, _, _) ->
    [];\r
-conversation_principale2(C, Messages, N, S) ->\r
-   case qlc:next_answers(C, 1) of\r
-      [] -> [];\r
-      [M] -> % traitement message par message (pas des plus performant :/)\r
-         Doit_etre_saute = any(fun(E) -> E == M end, Messages),\r
-         if  Doit_etre_saute -> \r
-               conversation_principale2(C, Messages, N, S); % le message ne fait pas partie de la conversation\r
-            S =:= 0 ->\r
-               [M | conversation_principale2(C, Messages, N - 1, S)]; % ok : le message fait partie de la conversation\r
-            true ->\r
-               conversation_principale2(C, Messages, N, S - 1) % on n'a pas encore atteint le début de la page\r
-         end\r
-   end.\r
+conversation_principale2(Id, Messages, N, S) ->
+   % traitement message par message (pas des plus performant :/)
+   Id_prev = mnesia:prev(minichat, Id),\r
+   Doit_etre_saute = any(fun(E) -> E == Id end, Messages),\r
+   if  Doit_etre_saute -> \r
+         conversation_principale2(Id_prev, Messages, N, S); % le message ne fait pas partie de la conversation\r
+      S =:= 0 ->\r
+         [Id | conversation_principale2(Id_prev, Messages, N - 1, S)]; % ok : le message fait partie de la conversation\r
+      true ->\r
+         conversation_principale2(Id_prev, Messages, N, S - 1) % on n'a pas encore atteint le début de la page\r
+      end.\r
    \r
    \r
 % Renvoie un tuple {C, Cn, X, Plus} où\r
index a34eae6..1fb3cc1 100755 (executable)
@@ -283,7 +283,7 @@ wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id
          {struct, [\r
             {reply, "new_troll"},\r
             {troll_id, Current#troll.id},\r
-            {message_id, euphorik_bd:message_id_associe(Current#troll.id)},\r
+            {message_id, Current#troll.id_minichat},\r
             {content, Current#troll.content}\r
          ]};\r
       _ ->\r
index c1bf49b..2cbc1ec 100644 (file)
@@ -22,6 +22,7 @@
 
 -module(euphorik_test).
 -export([
+   bench_write_minichat/1,
    start/2,
    stop/1,
    bench_get_messages/0,
@@ -34,6 +35,7 @@
 -define(INTERVALLE_MIN, 2).\r
 -define(INTERVALLE_MAX, 5).
 
+
 % N est le nombre d'utilisateur
 % M est le nombre de message que chaque utilisateur va poster
 start(N, M) ->
@@ -55,7 +57,7 @@ start(N, M) ->
 stop(Pids) ->
    lists:foreach(fun(Pid) -> exit(Pid, kill) end, Pids).
    
-
+% des trucs qui trainent
 bench_get_messages() ->
    T = [
       {page,"chat"},
@@ -67,8 +69,6 @@ bench_get_messages() ->
       {conversations,{array,[]}}
    ],
    moyenne_temps(euphorik_protocole, wait_event, [T], 20).
-
-
 bench_get_messages_avec_2_conversations() ->
    T = [
       {page,"chat"},
@@ -91,8 +91,6 @@ bench_get_messages_avec_2_conversations() ->
       ]}}
    ],
    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) ->
@@ -129,9 +127,9 @@ mot_rand(L, 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.7
-%  2 : 0.15
-%  3 : 0.05
+%  1 : 0.95
+%  2 : 0.04
+%  3 : 0.01
 messages_id_rand() ->
    R = random:uniform(),
    if R =< 0.1 ->
@@ -139,9 +137,9 @@ messages_id_rand() ->
       true ->
          Messages = lists:map(fun(#minichat{id = Id}) -> Id end, euphorik_bd:messages(8)),
          if
-            R > 0.1 andalso R =< 0.8 ->
+            R > 0.1 andalso R =< 0.95 ->
                tire_element_rand(1, Messages);
-            R > 0.8 andalso R =< 0.95 ->
+            R > 0.95 andalso R =< 0.99 ->
                tire_element_rand(2, Messages);
             true ->
                tire_element_rand(3, Messages)
@@ -170,9 +168,8 @@ 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()},\r
-   %{Message, Repond_a} = {"blablablablablabla", []},
-   io:format("~p poste ~p et repond a ~w~n", [User_id, Message, Repond_a]),
+   {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]),
@@ -181,4 +178,35 @@ 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
index 6713830..0b04c4b 100755 (executable)
@@ -1,3 +1,4 @@
+% coding: utf-8
 % Copyright 2008 Grégory Burri
 %
 % This file is part of Euphorik.
@@ -19,7 +20,7 @@
 
 
 % Version de la BD
--define(VERSION_BD, 2).
+-define(VERSION_BD, 3).
 -define(TABLES, [counter, proprietes, minichat, reponse_minichat, user, ip_table, troll]).
 
 
@@ -55,7 +56,6 @@
       date, % erlang:now()
       pseudo, % chaine de caractère
       contenu, % chaine de caractère
-      troll_id = undefined, % l'id du troll associé correspondant
       racine_id = undefined % la racine, par défaut correspond à l'id du message
    }).
    
@@ -69,7 +69,6 @@
    }). 
 
 
-
 -record(profile, % attention : pas une table !
    {
       pseudo = [], % string()
@@ -81,7 +80,9 @@
       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?}
-   }).
+   }).\r
+   \r
+   
 -record(user,
    {
       id,
 -record(troll,
    {
       id,
-      id_user,
+      id_user,\r
+      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
index 4db3de1..54aef62 100755 (executable)
@@ -22,6 +22,15 @@ body {
        margin: 0px;
 }
 
+/***** Textile *****/
+em.leger {
+       font-style: italic
+}
+em.fort {
+       font-style: normal;
+       font-weight: bold
+}
+
 /***** Menu *****/
 ul#menu {
        background-image: url(img/logo_fond.png);
@@ -221,6 +230,7 @@ div#info .boutons div:hover {
 form input,
 form button,
 form select {
+       color: #841919;
        background-color: #f0df95; 
        border: #841919 1px solid;
 }
index ebfadcf..de908dd 100755 (executable)
@@ -9,6 +9,18 @@
        vertical-align: middle;
 }
 
+/***** L'aide sur les commandes *****/
+#aideCommandes h1 {
+       font-size: 14px;
+       font-weight: bold;
+}
+#aideCommandes .usage {
+       font-weight: bold;
+}
+#aideCommandes li {
+       list-style-type: none;
+}
+
 /***** La boite de sélection des smiles *****/
 #smiles {
        text-align: center;
        top: 77px;
        line-height: 32px;
 }
+
+#page.minichat #trollCourant a:link,  #page.minichat #trollCourant a:visited {
+       color: #f85b31;
+}
+ #page.minichat #trollCourant a:hover,  #page.minichat #trollCourant a:active {
+       color: #f87e5d;
+}
+
 #page.minichat #trollCourant .troll {
        cursor: pointer;
        font-style: italic
index 52f959f..261d8b3 100755 (executable)
@@ -25,6 +25,15 @@ body {
    margin-top: 40px;
 }
 
+/***** Textile *****/
+em.leger {
+       font-style: italic
+}
+em.fort {
+       font-style: normal;
+       font-weight: bold
+}
+
 /***** Menu *****/
 ul#menu {
        padding-left: 300px;
index 633ff61..67b4d58 100755 (executable)
        vertical-align: middle;
 }
 
+/***** L'aide sur les commandes *****/
+#aideCommandes h1 {
+       font-size: 14px;
+       font-weight: bold;
+}
+#aideCommandes .usage {
+       font-weight: bold;
+}
+#aideCommandes li {
+       list-style-type: none;
+}
+
 /***** La boite de sélection des smiles *****/
 #smiles {
        text-align: center;
index 2b9cb17..933aceb 100755 (executable)
@@ -5,13 +5,39 @@
 % Recharge les modules de euphorik et met à jour la BD.
 % TODO : construire le nom du noeud en fonction du nom de l'host
 
-main(_) -> 
+hote() ->
+   '@overnux'.
+
+% le premier argument est le nom du noeud est peut valoir :
+% - yaws : noeud de production
+% - yaws_dev : noeud de pre-production
+main([Nom_node]) when Nom_node =:= "yaws"; Nom_node =:= "yaws_dev" -> 
+   Node = list_to_atom(Nom_node ++ atom_to_list(hote())),
    net_kernel:start([flynux, shortnames]),
    io:format("rechargement des modules..~n"),
-   _Pid = spawn_link(yaws@overnux, euphorik_daemon, reload_euphorik, []),
-   receive
-      {'EXIT', _, _} ->
-         io:format("mise à jour de la BD..~n"),
-         spawn(yaws@overnux, euphorik_bd_admin, update, [])
-   end.
+   rpc:call(Node, euphorik_daemon, reload_euphorik, []),
+   if Nom_node =:= "yaws_dev" -> copier_bd(Node);
+      true -> true
+   end,
+   io:format("mise à jour de la BD..~n"),
+   rpc:call(Node, euphorik_bd_admin, update, []);
+main(_) ->
+   io:format("Usage : mise_en_prod.erl <node>"),
+   halt(1).
 
+% Copie la bd du noeud de production
+copier_bd(Node) ->
+   io:format("Copie de la BD de production vers le noeude pre-production~n"),
+   Fichier = "/tmp/backup_ek_tmp",
+   Fichier2 = "/tmp/backup_ek_tmp2",
+   rpc:call(yaws@overnux, mnesia, backup, [Fichier]),
+   rpc:call(Node, euphorik_bd_admin, change_node_name, [yaws@overnux, yaws_dev@overnux, Fichier, Fichier2]),
+   rpc:call(Node, mnesia, restore, [Fichier2, [{default_op, recreate_tables}]]),
+   rpc:call(yaws@overnux, file, delete, [Fichier]),
+   rpc:call(Node, file, delete, [Fichier2]).
+   
+   
+   
+   
+   
+   
index 8b9311e..d812137 100755 (executable)
@@ -1,3 +1,4 @@
 #!/bin/bash
+# coding: utf-8
 # screen est utilisé par exemple pour lancé une version de preproduction et permettre de la faire tourner après un délog
-yaws --conf ./yaws.conf --sname yaws_dev --mnesiadir "../var/BD/" -I debian_yaws_dev
+yaws --conf ./yaws.conf --sname yaws_dev --mnesiadir "../var/BD/" -I debian_yaws_dev\r
index 11570dd..b437687 100644 (file)
@@ -49,15 +49,16 @@ class VerifJS
    
    def verifierRecur(dossier)
       Dir.foreach(dossier){|fichier|
-         if fichier != '.' and fichier != '..' and File.directory?(fichier) and fichier != 'dirs'
-            if not verifierRecur(dossier + '/' + fichier)
+         cheminComplet = "#{dossier}/#{fichier}"
+         if fichier[0,1] != '.' and File.directory?(cheminComplet) and fichier != 'libs'
+            if not verifierRecur(cheminComplet)
                return false
             end
          elsif fichier[-3, 3] == '.js'
-            puts "== Vérification de #{dossier}/#{fichier} =="
+            puts "== Vérification de #{cheminComplet} =="
             # TODO : mettre un if pour la version windows si dessous 
-            #system("java org.mozilla.javascript.tools.shell.Main jslint.js #{dossier}/#{fichier}")
-            system("rhino ./tools/jslint.js #{dossier}/#{fichier}")
+            #system("java org.mozilla.javascript.tools.shell.Main jslint.js #{cheminComplet}")
+            system("rhino ./tools/jslint.js #{cheminComplet}")
             # puts $?.exitstatus
             if $?.exitstatus > 0
                return false
@@ -116,7 +117,7 @@ class MiseEnProd
    # Effectue la mise en production.
    def miseEnProd
       copierFichiers()
-      maj()
+      maj('yaws')
    end
    
    # Effectue la mise en préproduction.
@@ -124,6 +125,7 @@ class MiseEnProd
       copierFichiers()
       copierVAR()
       lancerYaws()
+      maj('yaws_dev')
    end
    
    def copierFichiers
@@ -144,7 +146,7 @@ class MiseEnProd
       creer_rep("tools")
       system("rsync tools/yaws.conf #{@uri}:#{@rep}/tools")
       system("rsync tools/start_yaws.sh #{@uri}:#{@rep}/tools")
-      # TODO
+      system("ssh #{@uri} \"cd #{@rep}/tools; screen -d -m -S yaws_dev ./start_yaws.sh\"")
    end
    
    def exec(commande)
@@ -159,6 +161,7 @@ class MiseEnProd
    end
    
    def compiler_partie_serveuse
+      log "compilation des modules serveur"
       Dir.chdir('modules')
       system("make")
       if $?.exitstatus != 0
@@ -178,6 +181,7 @@ class MiseEnProd
    
    # css, images, html, etc..
    def copier_partie_statique
+      log "copie de la partie statique"      
       uri = "#{@uri}:#{@rep}"
       system("awk '$0 !~ /prod=\"delete\"/' index.yaws | ssh #{@uri} \" cat > #{@rep}/index.yaws\"")
       system("rsync favicon.ico #{uri}")
@@ -188,6 +192,7 @@ class MiseEnProd
    
    # minification et package des fichiers js dans euphorik.js
    def pack_js
+      log "minification, assemblage et copie du javascript" 
       rep_js = 'js'
       creer_rep(rep_js)
       # jquery.js et euphorik.js doivent se trouve en premier
@@ -217,6 +222,7 @@ class MiseEnProd
    end
    
    def copie_modules_serveurs      
+      log "copie des modules du serveur" 
       # copie des modules erlang
       creer_rep('modules')
       system("rsync -r --exclude 'euphorik_test.beam' modules/ebin #{@uri}:#{@rep}/modules")
@@ -224,14 +230,24 @@ class MiseEnProd
    end
    
    def set_droits_fichiers
+      log "attribution des droits sur les fichiers" 
       # attribution des droits
       exec("chmod -R g+rx .")
    end
    
-   def maj
+   # noeud :  le nom du noeud sur lequel le script de mise en prod est exécuté
+   # Execute le script 'mise_en_prod.erl' sur le serveur afin de :
+   # - Recharger les modules
+   # - Mettre à jour la base de données
+   def maj(noeud)
+      log "rechargement des modules serveur et mise à jour de la base de données" 
       # execution du script de mise à jour
       system("cat tools/mise_en_prod.erl | ssh #{@uri} \"cat > /tmp/mise_en_prod.erl\"")
-      system("ssh #{@uri} \"chmod u+x /tmp/mise_en_prod.erl; /tmp/mise_en_prod.erl; rm /tmp/mise_en_prod.erl\"")
+      system("ssh #{@uri} \"chmod u+x /tmp/mise_en_prod.erl; /tmp/mise_en_prod.erl #{noeud}; rm /tmp/mise_en_prod.erl\"")
+   end
+      
+   def log(message)
+      puts "----- #{message} -----"
    end
 end
 
@@ -264,18 +280,7 @@ class Commande
             @verifJS.verifier()
          when 'version'
             @version.maj()
-      end
-      
-=begin
-      Net::SSH.start('euphorik.ch', 'gburri') {|ssh|
-         output = ssh.exec!("hostname")
-         stdout = ""
-         ssh.exec!("ls -l /tmp"){|channel, stream, data|
-            stdout << data if stream == :stdout
-         }
-         puts stdout
-      }
-=end
+         end
    end
    
    def afficherUsage