ADD changement dans le protocole, le premier message de chaque conversation est maint...
authorGreg Burri <greg.burri@gmail.com>
Thu, 26 Jun 2008 22:42:32 +0000 (22:42 +0000)
committerGreg Burri <greg.burri@gmail.com>
Thu, 26 Jun 2008 22:42:32 +0000 (22:42 +0000)
doc/protocole3.txt
modules/erl/euphorik_bd.erl
modules/erl/euphorik_minichat_conversation.erl
modules/erl/euphorik_protocole.erl

index 0af2cce..12b3f1f 100644 (file)
@@ -37,14 +37,14 @@ Message ok générique :
 <ok>
    {
       "reply" : "ok"
 <ok>
    {
       "reply" : "ok"
-   }\r
-\r
-Entete des messages clients :\r
-Si la version du protocole n'est pas similaire du coté serveur la requête est refusée\r
-<client_header>\r
-   {\r
-      "action" : <action>,\r
-      "version" : 3\r
+   }
+
+Entete des messages clients :
+Si la version du protocole n'est pas similaire du coté serveur la requête est refusée
+<client_header>
+   {
+      "action" : <action>,
+      "version" : 3
    }
 
 
    }
 
 
@@ -65,7 +65,7 @@ ou
       "cookie" : "LKJDLAKSJBFLKASN"
    }
 ou
       "cookie" : "LKJDLAKSJBFLKASN"
    }
 ou
-   {\r
+   {
       "header" : {action : "authentification", version : 3},
       "login" : "paul",
       "password" : "IJKJDHHSAD9081238"
       "header" : {action : "authentification", version : 3},
       "login" : "paul",
       "password" : "IJKJDHHSAD9081238"
@@ -101,7 +101,7 @@ ou
  
 === Logout ===
 c -> s
  
 === Logout ===
 c -> s
-   {\r
+   {
       "header" : {action : "logout", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN"
    }
       "header" : {action : "logout", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN"
    }
@@ -109,7 +109,7 @@ c -> s
  
 === Profile ===
 c -> s
  
 === Profile ===
 c -> s
-   {\r
+   {
       "header" : {action : "set_profile", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "login" : "paul49",
       "header" : {action : "set_profile", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "login" : "paul49",
@@ -134,28 +134,28 @@ ou
 Si "last_message_id" est absent alors le client ne possède pas de message.
 Si "main_page" est absent alors est vaut 1.
 "cookie" n'est pas obligatoire.
 Si "last_message_id" est absent alors le client ne possède pas de message.
 Si "main_page" est absent alors est vaut 1.
 "cookie" n'est pas obligatoire.
-\r
-<message>\r
-   {\r
-      "id" : 54,\r
-      "user_id" : 344,\r
-      "date" : "Hier 17:26:54",\r
-      "system" : true | false,\r
-      "owner" : true | false,\r
-      "answered" : true | false,\r
-      "is_a_reply" : true | false,\r
-      "nick" : "Paul",\r
-      "login" : "paul_22",\r
-      "content" : "Salut",\r
-      "root" : 453,\r
-      "answer_to" : [\r
-         { "id" : 123, "nick" : "Pierre", "login" : "pierre_45" }\r
-      ]\r
-      "ek_master" : true | false\r
-   }\r
+
+<message>
+   {
+      "id" : 54,
+      "user_id" : 344,
+      "date" : "Hier 17:26:54",
+      "system" : true | false,
+      "owner" : true | false,
+      "answered" : true | false,
+      "is_a_reply" : true | false,
+      "nick" : "Paul",
+      "login" : "paul_22",
+      "content" : "Salut",
+      "root" : 453,
+      "answer_to" : [
+         { "id" : 123, "nick" : "Pierre", "login" : "pierre_45" }
+      ]
+      "ek_master" : true | false
+   }
 
 c -> s
 
 c -> s
-   {\r
+   {
       "header" : {action : "wait_event", version : 3},
       "page" : "chat"
       "cookie" : "LKJDLAKSJBFLKASN",
       "header" : {action : "wait_event", version : 3},
       "page" : "chat"
       "cookie" : "LKJDLAKSJBFLKASN",
@@ -176,12 +176,13 @@ s -> c
 La première conversation est la principale (main).
 L'ordre des conversation est le même que celui des données de l'utilisateur.
 Le format de la date n'est pas formel.
 La première conversation est la principale (main).
 L'ordre des conversation est le même que celui des données de l'utilisateur.
 Le format de la date n'est pas formel.
+first correpond au premier message de la conversation, vaut 'undefined' pour la conversation principale ainsi que pour les conversations vides.
    {
       "reply" : "new_message",
       "conversations" : [
          {
    {
       "reply" : "new_message",
       "conversations" : [
          {
-            "last_page" : true | false,\r
-            "first" : <message>,
+            "last_page" : true | false,
+            "first" : <message> | undefined,
             "messages" : [ <message>, .. ]
          }
       ]
             "messages" : [ <message>, .. ]
          }
       ]
@@ -205,7 +206,7 @@ ou
 
 === Wait event (page = admin) ===
 c -> s
 
 === Wait event (page = admin) ===
 c -> s
-   {\r
+   {
       "header" : {action : "wait_event", version : 3},
       "page" : "admin",
       "last_troll" : 5
       "header" : {action : "wait_event", version : 3},
       "page" : "admin",
       "last_troll" : 5
@@ -247,7 +248,7 @@ s -> c
 
 === Envoie d'un troll ===
 c -> s
 
 === Envoie d'un troll ===
 c -> s
-   {\r
+   {
       "header" : {action : "put_troll", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "content" : "Un bon troll velu !"
       "header" : {action : "put_troll", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "content" : "Un bon troll velu !"
@@ -261,7 +262,7 @@ ou
    
 === Modification d'un troll ===
 c -> s
    
 === Modification d'un troll ===
 c -> s
-   {\r
+   {
       "header" : {action : "mod_troll", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "troll_id" : 3,
       "header" : {action : "mod_troll", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "troll_id" : 3,
@@ -276,7 +277,7 @@ ou
    
 === Suppression d'un troll ===
 c -> s
    
 === Suppression d'un troll ===
 c -> s
-   {\r
+   {
       "header" : {action : "del_troll", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "troll_id" : 3
       "header" : {action : "del_troll", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "troll_id" : 3
@@ -293,7 +294,7 @@ Le client envoie un message, le message peut répondre à un certain nombre d'au
 "answer_to" n'est pas obligatoire.
 
 c -> s
 "answer_to" n'est pas obligatoire.
 
 c -> s
-   {\r
+   {
       "header" : {action : "put_message", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "nick" : "Paul",
       "header" : {action : "put_message", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "nick" : "Paul",
@@ -309,7 +310,7 @@ ou
 
 === Slapage ===
 c -> s
 
 === Slapage ===
 c -> s
-   {\r
+   {
       "header" : {action : "slap", version : 3},
       "cookie" :  "LKJDLAKSJBFLKASN",
       "user_id" : 67,
       "header" : {action : "slap", version : 3},
       "cookie" :  "LKJDLAKSJBFLKASN",
       "user_id" : 67,
@@ -324,7 +325,7 @@ ou
 
 === Bannissement ===
 c -> s
 
 === Bannissement ===
 c -> s
-   {\r
+   {
       "header" : {action : "ban", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "duration" : 15, // en minute
       "header" : {action : "ban", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "duration" : 15, // en minute
@@ -340,7 +341,7 @@ ou
    
 === Liste des ip bannis ===
 c -> s
    
 === Liste des ip bannis ===
 c -> s
-   {\r
+   {
       "header" : {action : "list_banned_ips", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN"
    }
       "header" : {action : "list_banned_ips", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN"
    }
@@ -365,7 +366,7 @@ s -> c
 
 === Débannissement ===
 c -> s
 
 === Débannissement ===
 c -> s
-   {\r
+   {
       "header" : {action : "unban", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN"
       "ip" : "192.168.1.2"
       "header" : {action : "unban", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN"
       "ip" : "192.168.1.2"
@@ -383,7 +384,7 @@ Le message est appondu avec un " +++ " devant, par exemple :
 > Gnome c'est mieux que KDE +++ Euh non ok, c'est faux
 
 c -> s
 > Gnome c'est mieux que KDE +++ Euh non ok, c'est faux
 
 c -> s
-   {\r
+   {
       "header" : {action : "correction", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "content" : "Euh non ok, c'est faux"
       "header" : {action : "correction", version : 3},
       "cookie" : "LKJDLAKSJBFLKASN",
       "content" : "Euh non ok, c'est faux"
index ed07a56..be46ab2 100755 (executable)
@@ -61,6 +61,8 @@
    reponses/0,\r
    parents/1,\r
    enfants/1,\r
    reponses/0,\r
    parents/1,\r
    enfants/1,\r
+   parents_id/1,\r
+   enfants_id/1,\r
    est_une_reponse_a_user/2,\r
    a_repondu_a_message/2,\r
    possede_message/2,\r
    est_une_reponse_a_user/2,\r
    a_repondu_a_message/2,\r
    possede_message/2,\r
@@ -619,6 +621,36 @@ enfants(M_id) ->
       end\r
    )).\r
    \r
       end\r
    )).\r
    \r
+   \r
+% Renvoie les parents d'un message M (les messages auquels répond M)\r
+% ordrés du plus petit au plus grand..\r
+% @spec parents_id(integer()) -> [integer()]\r
+parents_id(M) ->\r
+   resultat_transaction(mnesia:transaction(fun() ->\r
+      e(\r
+         qlc:sort(\r
+            q([E#reponse_minichat.cible || E <- mnesia:table(reponse_minichat), E#reponse_minichat.repondant =:= M]),\r
+            [{order, ascending}]\r
+         ),\r
+         [{tmpdir, ?KEY_SORT_TEMP_DIR}]\r
+      )\r
+   end)).\r
+   \r
+   \r
+% Renvoie les id des enfants d'un message M (les messages qui répondent à M)\r
+% ordrés du plus petit au plus grand.\r
+% @spec enfants_id(integer()) -> [integer()]\r
+enfants_id(M) ->\r
+   resultat_transaction(mnesia:transaction(fun() ->\r
+      e(\r
+         qlc:sort(\r
+            q([E#reponse_minichat.repondant || E <- mnesia:table(reponse_minichat), E#reponse_minichat.cible =:= M]),\r
+            [{order, ascending}]\r
+         ),\r
+         [{tmpdir, ?KEY_SORT_TEMP_DIR}]\r
+      )\r
+   end)).\r
+     \r
 \r
 % Est-ce que le message Id_mess est une réponse d'une message de Id_user ?\r
 est_une_reponse_a_user(Id_user, Id_mess) ->\r
 \r
 % Est-ce que le message Id_mess est une réponse d'une message de Id_user ?\r
 est_une_reponse_a_user(Id_user, Id_mess) ->\r
index e46ca91..2f932e2 100755 (executable)
 %\r
 % You should have received a copy of the GNU General Public License\r
 % along with Euphorik.  If not, see <http://www.gnu.org/licenses/>.\r
 %\r
 % You should have received a copy of the GNU General Public License\r
 % along with Euphorik.  If not, see <http://www.gnu.org/licenses/>.\r
-% 
-% 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
-% 
-% @type Message() = {integer(), [integer()]}
-% @type Conversation_detailee() = {[integer()], [integer()], [integer()], bool()}
-% @type Conversation = {[Message()] , bool()} bool() : si true alors il y a encore des messages dans les pages suivantes.
+% \r
+% Ce module permet la gestion des conversations du minichat d'euphorik.\r
+% Un message (enfant) peut répondre à des messages (ses parents).\r
+% Un message (parent) peut avoir plusieurs réponses (enfants)\r
+% @author G.Burri\r
+% \r
+% Les conversation se compose d'une liste de tuple comprenant la conversation, du premier message de la conversation,\r
+% et d'un booleen indiquant s'il y a encore des messages\r
+% @type Conversations() = [{Conversation(), Message(), bool()}]\r
+% \r
+% Une conversation est simplement une liste de messages\r
+% @type Conversation() = [Message()]\r
+%\r
+% 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
 \r
-
 -module(euphorik_minichat_conversation).\r
 -export([\r
    conversations/4\r
 -module(euphorik_minichat_conversation).\r
 -export([\r
    conversations/4\r
-]).
--include("../include/euphorik_bd.hrl").
--include("../include/euphorik_defines.hrl").
--include_lib("stdlib/include/qlc.hrl").
--import(lists, [reverse/1, any/2, map/2, sublist/3, filter/2]).
--import(euphorik_bd, [resultat_transaction/1]).
--import(qlc, [e/2, q/1]).
--import(mnesia, [table/1, transaction/1]).
-  
-   
-% Renvoie les conversations sous la forme d'une liste de conversation.
-% Chaque conversation est un tuple {[{Message, Parents}], Plus} où
-% Message est le message de type #minichat et Parents une liste d'Id.
-% Plus est un bool. Si Plus vaut true alors il y a encore des messages.
-% Si il n'y a pas de nouveaux message alors vide est renvoyé.
-% Chaque racine est un tuple {R, P,  D}
-% N : le nombre de message
-% D : le dernier message connu, 0 si aucun
-% P : la page souhaité, la premier est la 1
-% @spec conversations([{integer(), integer(), integer()}], integer(), integer(), integer()) -> [Conversation()]
-conversations(Racines, N, D, P) ->
-      Conversations = conversations_detailees(Racines, N, D, P),
-      % si les conversations sont vides alors on attend un nouveau message
-      Vide = not any(
-         fun(C) ->
-            case C of
-               {[], _} -> false;
-               {_, [], _, _} -> false;
-               _ -> true
-            end
-         end,
-         Conversations
-      ),
-      if Vide ->
-            vide;
-         true ->
-            mise_en_forme_conversations(Conversations)
-      end.
-     
-
-% Mise en forme des conversations pour l'utilisateur du module.
-% @spec mise_en_forme_conversations([[integer()] | Conversation_detailee()]) -> [Conversation()]
-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, 5, 8, ...] -> [{4, [5, 6]}, ...].
-% Ajoute les parents de chaque message.
-% @spec mise_en_forme_conversation([integer()]) -> [{integer(), [integer()]}]
-mise_en_forme_conversation(Messages) ->
-   resultat_transaction(mnesia:transaction(
-      fun() ->
-         lists:foldr(
-            fun(Id, Acc) ->
-               case euphorik_bd:message_by_id(Id) of
-                  {ok, Message} ->
-                     [{Message, parents(Id)} | Acc];
-                  _ ->
-                     Acc
-               end
-            end,
-            [],
-            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}, 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()] | Conversation_detailee()]
-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() ->
-      Curseur = qlc:cursor(
-         qlc:sort(q([E#minichat.id || E <- table(minichat)]), [{order, descending}]),
-         [{tmpdir, ?KEY_SORT_TEMP_DIR}]
-      ),
-      {CP, Plus} = conversation_principale(Curseur, Conversations, N, P),
-      qlc:delete_cursor(Curseur),
-      {[M || M <- CP, M > D], Plus} % filtre en fonction de D
-   end)),
-   [Conversation_principale | Conversations].
-   
-
-% Construit la conversation principale en fonction d'un curseur C initialement placé sur le dernier message
-% et la liste de conversations.
-% N est le nombre de messages que l'on souhaite.
-% P est le numéro de la page (1, 2, 3...)
-% @spec conversation_principale(qlc:QueryCursor(), [Conversation_detailee()], integer(), integer()) -> {[Id], Plus}
-conversation_principale(C, Conversations, N, P) ->
-   % on prend en message de plus pour savoir s'il y en a plus que ce que l'on désire
-   CP = reverse(conversation_principale2(C, lists:flatten(map(fun({C2, _, X, _}) -> C2 -- X end, Conversations)), N + 1, (P - 1) * N)),
-   Plus = length(CP) =:= N + 1,
-   {
-      if Plus ->
-         [_| Suivants] = CP,
-         Suivants;
-      true ->
-         CP
-      end,
-      Plus
-   }.
-      
-      
-% C est le curseur (voir ci 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(qlc:QueryCursor(), [integer()], integer(), integer())
-conversation_principale2(_, _, 0, _) ->
-   [];
-conversation_principale2(C, Messages, N, S) ->
-   case qlc:next_answers(C, 1) of
-      [] -> [];
-      [M] -> % traitement message par message (pas des plus performant :/)
-         Doit_etre_saute = any(fun(E) -> E == M end, Messages),
-         if  Doit_etre_saute -> 
-               conversation_principale2(C, Messages, N, S); % le message ne fait pas partie de la conversation
-            S =:= 0 ->
-               [M | conversation_principale2(C, Messages, N - 1, S)]; % ok : le message fait partie de la conversation
-            true ->
-               conversation_principale2(C, Messages, N, S - 1) % on n'a pas encore atteint le début de la page
-         end
-   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
-% X : La liste des messages répondant à des mess qui ne font pas partie de la conversation
-% Plus : true s'il y a encore des messages après
-% Inputs :
-% R : l'id d'un message représentant la racine de la conversation
-% N : le nombre de message par page
-% D : Le dernier message connu 0 si aucun de connu
-% P : La page désirée
-% @spec conversation([integer()], integer(), integer(), integer()) -> Conversation_detailee()
-conversation(R, N, D, P) ->
-   {C, X} = conversation([], [R], []),
-   Decalage = N * (P - 1) + 1,
-   {
-      reverse(C),
-      if Decalage > length(C) ->
-            [];
-         true ->
-            filter(
-               fun(E) -> E > D end,
-               reverse(sublist(C, Decalage, N))
-            )
-      end,
-      reverse(X),
-      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()]) -> {}
-conversation(Conv, [M | Reste], X) ->
-   Est_deja_traite = any(fun(E) -> E =:= M end, Conv),
-   if  Est_deja_traite ->
-         conversation(Conv, Reste, X);
-      true ->
-         Enfants = enfants(M),
-         Parents = parents(M),
-         % un message est dit externe si un de ses parent ne fait pas partie de la conversation ou si un de ses parents fait partie de X
-         Est_message_externe =  Parents -- Conv =/= [] orelse intersection(Parents, X) =/= [],
-         conversation([M | Conv], lists:merge(Reste, Enfants), if Est_message_externe -> [M | X]; true -> X end)
-   end;
-conversation(Messages, [], X) ->
-   {Messages, X}.
-   
-   
-% Renvoie les enfants d'un message M (les messages qui répondent à M)
-% ordrés du plus petit au plus grand.
-% @spec enfants(integer()) -> [integer()]
-enfants(M) ->
-   resultat_transaction(transaction(fun() ->
-      e(
-         qlc:sort(
-            q([E#reponse_minichat.repondant || E <- table(reponse_minichat), E#reponse_minichat.cible =:= M]),
-            [{order, ascending}]
-         ),
-         [{tmpdir, ?KEY_SORT_TEMP_DIR}]
-      )
-   end)).
-   
-   
-% Renvoie les parents d'un message M (les messages auquels répond M)
-% ordrés du plus petit au plus grand..
-% @spec parents(integer()) -> [integer()]
-parents(M) ->
-   resultat_transaction(transaction(fun() ->
-      e(
-         qlc:sort(
-            q([E#reponse_minichat.cible || E <- table(reponse_minichat), E#reponse_minichat.repondant =:= M]),
-            [{order, ascending}]
-         ),
-         [{tmpdir, ?KEY_SORT_TEMP_DIR}]
-      )
-   end)).
-   
-
-% Intersection entre deux listes : [1, 3, 4] n [2, 4, 7] = [4]
-% @spec intersection(list(term()), list(term())) -> list(term())
-intersection(L1, L2) ->
-   filter(fun(X) -> lists:member(X, L1) end, L2).
-
+]).\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
+% Renvoie les conversations.\r
+% Chaque racine est un tuple {R, P, D}\r
+% R : l'id de la racine\r
+% N : le nombre de message\r
+% D : le dernier message connu, 0 si aucun\r
+% P : la page souhaité, la premier est la 1\r
+% @spec conversations([{integer(), integer(), integer()}], integer(), integer(), integer()) -> Conversations()\r
+conversations(Racines, N, D, P) ->\r
+      Conversations = conversations_detailees(Racines, N, D, P),\r
+      % si les conversations sont vides alors on attend un nouveau message\r
+      Vide = not any(\r
+         fun(C) ->\r
+            case C of\r
+               {[], _} -> false;\r
+               {_, [], _, _} -> false;\r
+               _ -> true\r
+            end\r
+         end,\r
+         Conversations\r
+      ),\r
+      if Vide ->\r
+            vide;\r
+         true ->\r
+            mise_en_forme_conversations(Conversations)\r
+      end.\r
+     \r
+\r
+% Mise en forme des conversations pour l'utilisateur du module.\r
+% @type Conversation_principale() = {[integer()], bool}\r
+% @type Conversation_detailee() = {[integer()], [integer()], [integer()], bool()}\r
+% @spec mise_en_forme_conversations([Conversation_principal() | Conversation_detailee()]) -> [Conversation()]\r
+mise_en_forme_conversations([]) -> [];\r
+mise_en_forme_conversations([{Principale, Plus_principale} | Conversations]) ->\r
+   [{mise_en_forme_conversation(Principale), Plus_principale} | map(fun({_, Cn, _, Plus}) -> {mise_en_forme_conversation(Cn), Plus} end, Conversations)].\r
+   \r
+   \r
+% Mise en forme d'une liste d'id de messages : [4, 9, 8, ...] -> [{#minichat, [5, 6]}, ...].\r
+% Ajoute les parents de chaque message.\r
+% @spec mise_en_forme_conversation([integer()]) -> [{#minichat, [integer()]}]\r
+mise_en_forme_conversation(Messages) ->\r
+   resultat_transaction(transaction(\r
+      fun() ->\r
+         lists:foldr(\r
+            fun(Id, Acc) ->\r
+               case euphorik_bd:message_by_id(Id) of\r
+                  {ok, Message} ->\r
+                     [{Message, euphorik_bd:parents_id(Id)} | Acc];\r
+                  _ ->\r
+                     Acc\r
+               end\r
+            end,\r
+            [],\r
+            Messages\r
+         )\r
+      end\r
+   )).\r
+\r
+   \r
+% Renvoie une liste de conversations, le première élément correspond à la conversation principale.\r
+% Les autres éléments sont des tuples {C, Cn, X, Plus}, voir conversation/4 pour plus d'infos.\r
+% Racines est une liste de tuple {Id, P} des racines des conversations ou P est la page et Id l'id du message.\r
+% @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
+      {[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
+% 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
+   % 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
+   Plus = length(CP) =:= N + 1,\r
+   {\r
+      if Plus ->\r
+         [_| Suivants] = CP,\r
+         Suivants;\r
+      true ->\r
+         CP\r
+      end,\r
+      Plus\r
+   }.\r
+      \r
+      \r
+% C est le curseur (voir ci 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
+conversation_principale2(_, _, 0, _) ->\r
+   [];\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
+   \r
+   \r
+% Renvoie un tuple {C, Cn, X, Plus} où\r
+% C : La conversation complète\r
+% Cn : La conversation tronqué en fonction de N, D et P\r
+% X : La liste des messages répondant à des mess qui ne font pas partie de la conversation\r
+% Plus : true s'il y a encore des messages après\r
+% Inputs :\r
+% R : l'id d'un message représentant la racine de la conversation\r
+% N : le nombre de message par page\r
+% D : Le dernier message connu 0 si aucun de connu\r
+% P : La page désirée\r
+% @spec conversation([integer()], integer(), integer(), integer()) -> Conversation_detailee()\r
+conversation(R, N, D, P) ->\r
+   {C, X} = conversation([], [R], []),\r
+   Decalage = N * (P - 1) + 1,\r
+   {\r
+      reverse(C),\r
+      if Decalage > length(C) ->\r
+            [];\r
+         true ->\r
+            filter(\r
+               fun(E) -> E > D end,\r
+               reverse(sublist(C, Decalage, N))\r
+            )\r
+      end,\r
+      reverse(X),\r
+      Decalage + N - 1 < length(C) \r
+   }.\r
+   \r
+   \r
+% 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\r
+% Attention : les messages de C et de X sont ordrés du plus grand Id au plus petit.\r
+% @spec conversation([integer()], [integer()], [integer()]) -> {[int()], [int()]}\r
+conversation(Conv, [M | Reste], X) ->\r
+   Est_deja_traite = any(fun(E) -> E =:= M end, Conv),\r
+   if  Est_deja_traite ->\r
+         conversation(Conv, Reste, X);\r
+      true ->\r
+         Enfants = euphorik_bd:enfants_id(M),\r
+         Parents = euphorik_bd:parents_id(M),\r
+         % un message est dit externe si un de ses parent ne fait pas partie de la conversation ou si un de ses parents fait partie de X\r
+         Est_message_externe =  Parents -- Conv =/= [] orelse intersection(Parents, X) =/= [],\r
+         conversation([M | Conv], lists:merge(Reste, Enfants), if Est_message_externe -> [M | X]; true -> X end)\r
+   end;\r
+conversation(Messages, [], X) ->\r
+   {Messages, X}.\r
+   \r
+\r
+% Intersection entre deux listes : [1, 3, 4] n [2, 4, 7] = [4]\r
+% @spec intersection(list(term()), list(term())) -> list(term())\r
+intersection(L1, L2) ->\r
+   filter(fun(X) -> lists:member(X, L1) end, L2).\r
+\r
index 448a20b..e8c1569 100755 (executable)
@@ -249,50 +249,48 @@ wait_event_page_chat(User, Racines_conversations, Message_count, Last_message_id
                   {reply, "new_messages"},\r
                   {conversations, {array,\r
                      lists:map(\r
                   {reply, "new_messages"},\r
                   {conversations, {array,\r
                      lists:map(\r
-                        fun({Conv, Plus}) ->\r
+                        fun({Racine, {Conv, Plus}}) ->\r
                            {struct, [\r
                               {last_page, not Plus},\r
                            {struct, [\r
                               {last_page, not Plus},\r
-                              {messages, {array, \r
+                              {first, \r
+                                 if Racine =:= undefined orelse Conv =:= [] ->\r
+                                       "undefined";\r
+                                    true ->\r
+                                       {Racine_id, _, _} = Racine,\r
+                                       case euphorik_bd:message_by_id(Racine_id) of\r
+                                          {ok, Mess} ->\r
+                                             json_message(Mess, euphorik_bd:parents(Racine), User);\r
+                                          _ ->\r
+                                             "undefined"\r
+                                       end\r
+                                 end\r
+                              }, % le premier message de la conversation, peut correspondre\r
+                              {messages, {array,\r
                                  lists:map(\r
                                  lists:map(\r
-                                    fun({Mess, Repond_a}) ->                                 \r
-                                       Est_proprietaire = User =/= inconnu andalso User#user.id =:= Mess#minichat.auteur_id,\r
-                                       A_repondu_a_message = User =/= inconnu andalso euphorik_bd:a_repondu_a_message(User#user.id, Mess#minichat.id),\r
-                                       Est_une_reponse_a_user = User =/= inconnu andalso euphorik_bd:est_une_reponse_a_user(User#user.id, Mess#minichat.id),\r
-                                       {ok, User_mess } = euphorik_bd:user_by_id(Mess#minichat.auteur_id),\r
-                                       {struct, [\r
-                                          {id, Mess#minichat.id},\r
-                                          {user_id, User_mess#user.id},\r
-                                          {date, format_date(Mess#minichat.date)},\r
-                                          {system, Mess#minichat.auteur_id =:= 0},\r
-                                          {owner, Est_proprietaire},\r
-                                          {answered, A_repondu_a_message},\r
-                                          {is_a_reply, Est_une_reponse_a_user},\r
-                                          {nick, Mess#minichat.pseudo},\r
-                                          {login, User_mess#user.login},\r
-                                          {content, Mess#minichat.contenu},\r
-                                          {root, Mess#minichat.racine_id},\r
-                                          {answer_to, {array, lists:map(\r
-                                             fun(Id_mess) ->                   \r
-                                                {ok, M} = euphorik_bd:message_by_id(Id_mess),\r
-                                                {ok, User_reponse} = euphorik_bd:user_by_mess(M#minichat.id),\r
-                                                {struct, [{id, M#minichat.id}, {nick, M#minichat.pseudo}, {login, User_reponse#user.login}]}\r
-                                             end,\r
-                                             Repond_a\r
-                                          )}},\r
-                                          {ek_master, User_mess#user.ek_master}\r
-                                       ]}\r
+                                    fun({Mess, Repond_a}) ->\r
+                                       json_message(Mess, Repond_a, User)\r
                                     end,\r
                                     Conv\r
                                  )\r
                               }}\r
                            ]}\r
                         end,\r
                                     end,\r
                                     Conv\r
                                  )\r
                               }}\r
                            ]}\r
                         end,\r
-                        Conversations\r
+                        % on ajoute un 'undefined' correspondant à la premier conversation qui ne possède pas de racine\r
+                        % TODO : peut être à revoir car un peu lourd est compliqué\r
+                        aggregation_racines_conversations([undefined | Racines_conversations], Conversations)\r
                      )\r
                   }}\r
                ]}\r
          end\r
    end.\r
                      )\r
                   }}\r
                ]}\r
          end\r
    end.\r
+   \r
+\r
+aggregation_racines_conversations(L1, L2) -> \r
+   aggregation_racines_conversations(L1, L2, []).\r
+aggregation_racines_conversations([], [], L) -> lists:reverse(L);\r
+aggregation_racines_conversations([E1|R1], [E2|R2], L) ->\r
+   aggregation_racines_conversations(R1, R2, [{E1, E2} | L]).\r
+   \r
 \r
 \r
 % Attend un événement lié à la page 'chat'.\r
 \r
 \r
 % Attend un événement lié à la page 'chat'.\r
@@ -664,3 +662,35 @@ json_reponse_login_ok(User) ->
          {ek_master, User#user.ek_master}\r
       ]\r
    }.\r
          {ek_master, User#user.ek_master}\r
       ]\r
    }.\r
+   \r
+% Renvoie le message formaté en JSON.\r
+% Mess est de type #minichat\r
+% Repond_a est une liste d'id des messages auquel répond Mess\r
+% User est l'utilisateur courant de type #user\r
+json_message(Mess, Repond_a, User) ->\r
+   Est_proprietaire = User =/= inconnu andalso User#user.id =:= Mess#minichat.auteur_id,\r
+   A_repondu_a_message = User =/= inconnu andalso euphorik_bd:a_repondu_a_message(User#user.id, Mess#minichat.id),\r
+   Est_une_reponse_a_user = User =/= inconnu andalso euphorik_bd:est_une_reponse_a_user(User#user.id, Mess#minichat.id),\r
+   {ok, User_mess } = euphorik_bd:user_by_id(Mess#minichat.auteur_id),\r
+   {struct, [\r
+      {id, Mess#minichat.id},\r
+      {user_id, User_mess#user.id},\r
+      {date, format_date(Mess#minichat.date)},\r
+      {system, Mess#minichat.auteur_id =:= 0},\r
+      {owner, Est_proprietaire},\r
+      {answered, A_repondu_a_message},\r
+      {is_a_reply, Est_une_reponse_a_user},\r
+      {nick, Mess#minichat.pseudo},\r
+      {login, User_mess#user.login},\r
+      {content, Mess#minichat.contenu},\r
+      {root, Mess#minichat.racine_id},\r
+      {answer_to, {array, lists:map(\r
+         fun(Id_mess) ->                   \r
+            {ok, M} = euphorik_bd:message_by_id(Id_mess),\r
+            {ok, User_reponse} = euphorik_bd:user_by_mess(M#minichat.id),\r
+            {struct, [{id, M#minichat.id}, {nick, M#minichat.pseudo}, {login, User_reponse#user.login}]}\r
+         end,\r
+         Repond_a\r
+      )}},\r
+      {ek_master, User_mess#user.ek_master}\r
+   ]}.\r