- Modify network protocol to use new delimiter
authorunc0rr
Mon, 21 Jul 2008 09:45:40 +0000
changeset 1082 596b1dcdc1df
parent 1081 5be338fa4e2c
child 1083 3448dd03483f
- Modify network protocol to use new delimiter - Much improve dedicated server usability
QTfrontend/netconnectedclient.cpp
QTfrontend/netconnectedclient.h
QTfrontend/newnetclient.cpp
QTfrontend/newnetclient.h
netserver/HWProto.hs
netserver/Miscutils.hs
netserver/newhwserv.hs
--- a/QTfrontend/netconnectedclient.cpp	Tue Jul 15 16:40:50 2008 +0000
+++ b/QTfrontend/netconnectedclient.cpp	Mon Jul 21 09:45:40 2008 +0000
@@ -47,19 +47,22 @@
 void HWConnectedClient::ClientRead()
 {
   try {
-    while (m_client->canReadLine()) {
-      ParseLine(m_client->readLine().trimmed());
-    }
+	while (m_client->canReadLine()) {
+		QString s = QString::fromUtf8(m_client->readLine().trimmed());
+		if (s.size() == 0) {
+			ParseCmd(cmdbuf);
+			cmdbuf.clear();
+		} else
+			cmdbuf << s;
+	}
   } catch(ShouldDisconnectException& e) {
     m_client->close();
   }
 }
 
-void HWConnectedClient::ParseLine(const QByteArray & line)
+void HWConnectedClient::ParseCmd(const QStringList & lst)
 {
-  QString msg = QString::fromUtf8 (line.data(), line.size());
-  QStringList lst = msg.split(delimeter);
-//qDebug() << "Parsing: " << lst;
+qDebug() << "Server: Parsing:" << lst;
   if(!lst.size())
   {
     qWarning("Net server: Bad message");
@@ -101,7 +104,7 @@
 
   if(client_nick=="")
   {
-  	qWarning(QString("Net server: Message from unnamed client: '%1'").arg(msg).toAscii().data());
+  	qWarning() << "Net server: Message from unnamed client:" << lst;
   	return;
   }
 
@@ -117,7 +120,7 @@
 
   if(lst[0]=="HHNUM") {
     if (lst.size()<4) {
-      qWarning((QString("Net server: Bad 'HHNUM' message: ")+msg+" size="+QString("%1").arg(lst.size())).toAscii().data());
+      qWarning() << "Net server: Bad 'HHNUM' message:" << lst;
       return;
     }
     if(!m_hwserver->isChiefClient(this))
@@ -131,15 +134,16 @@
     m_hwserver->hhnum+=newTeamHHNum-oldTeamHHNum;
 qDebug() << "HHNUM hhnum = " << m_hwserver->hhnum;
     // create CONFIG_PARAM to save HHNUM at server from lst
-    lst=QStringList("CONFIG_PARAM") << confstr << lst[3];
-    m_hwserver->sendOthers(this, lst.join(QString(delimeter)));
-    m_hwserver->m_gameCfg[lst[1]]=lst.mid(2);
+    QStringList tmp = lst;
+    tmp=QStringList("CONFIG_PARAM") << confstr << lst[3];
+    m_hwserver->sendOthers(this, tmp.join(QString(delimeter)));
+    m_hwserver->m_gameCfg[tmp[1]]=tmp.mid(2);
     return;
   }
 
   if(lst[0]=="CONFIG_PARAM") {
     if (lst.size()<3) {
-      qWarning((QString("Net server: Bad 'CONFIG_PARAM' message: ")+msg).toAscii().data());
+      qWarning() << "Net server: Bad 'CONFIG_PARAM' message:" << lst;
       return;
     }
 
@@ -156,11 +160,12 @@
       qWarning("Net server: Bad 'ADDTEAM' message");
 	  return;
     }
-    lst.pop_front();
+    QStringList tmp = lst;
+    tmp.pop_front();
 
     // add team ID
     static unsigned int netTeamID=0;
-    lst.insert(1, QString::number(++netTeamID));
+    tmp.insert(1, QString::number(++netTeamID));
 
     // hedgehogs num count
     int maxAdd=18-m_hwserver->hhnum;
@@ -173,21 +178,21 @@
     m_hwserver->hhnum+=toAdd;
 qDebug() << "to add = " << toAdd << "m_hwserver->hhnum = " << m_hwserver->hhnum;
     // hedgehogs num config
-    QString hhnumCfg=QString("CONFIG_PARAM%1HHNUM+%2+%3%1%4").arg(delimeter).arg(lst[0])\
+    QString hhnumCfg=QString("CONFIG_PARAM%1HHNUM+%2+%3%1%4").arg(delimeter).arg(tmp[0])\
       .arg(netTeamID)\
       .arg(toAdd);
 
     // creating color config for new team
-    QString colorCfg=QString("CONFIG_PARAM%1TEAM_COLOR+%2+%3%1%4").arg(delimeter).arg(lst[0])\
+    QString colorCfg=QString("CONFIG_PARAM%1TEAM_COLOR+%2+%3%1%4").arg(delimeter).arg(tmp[0])\
       .arg(netTeamID)\
-      .arg(lst.takeAt(2));
+      .arg(tmp.takeAt(2));
 
     m_hwserver->m_gameCfg[colorCfg.split(delimeter)[1]]=colorCfg.split(delimeter).mid(2);
     m_hwserver->m_gameCfg[hhnumCfg.split(delimeter)[1]]=hhnumCfg.split(delimeter).mid(2);
-    m_teamsCfg.push_back(lst);
+    m_teamsCfg.push_back(tmp);
 
-    m_hwserver->sendOthers(this, QString("ADDTEAM:")+delimeter+lst.join(QString(delimeter)));
-    RawSendNet(QString("TEAM_ACCEPTED%1%2%1%3").arg(delimeter).arg(lst[0]).arg(lst[1]));
+    m_hwserver->sendOthers(this, QString("ADDTEAM:")+delimeter+tmp.join(QString(delimeter)));
+    RawSendNet(QString("TEAM_ACCEPTED%1%2%1%3").arg(delimeter).arg(tmp[0]).arg(tmp[1]));
     m_hwserver->sendAll(colorCfg);
     m_hwserver->sendAll(hhnumCfg);
     return;
@@ -219,7 +224,7 @@
     return;
   }
 
-  m_hwserver->sendOthers(this, msg);
+  m_hwserver->sendOthers(this, lst.join(QString(delimeter)));
 }
 
 unsigned int HWConnectedClient::removeTeam(const QString& tname)
@@ -251,7 +256,7 @@
 void HWConnectedClient::RawSendNet(const QByteArray & buf)
 {
   m_client->write(buf);
-  m_client->write("\n", 1);
+  m_client->write("\n\n", 2);
 }
 
 QString HWConnectedClient::getClientNick() const
--- a/QTfrontend/netconnectedclient.h	Tue Jul 15 16:40:50 2008 +0000
+++ b/QTfrontend/netconnectedclient.h	Mon Jul 21 09:45:40 2008 +0000
@@ -22,6 +22,7 @@
 #include <QObject>
 #include <QList>
 #include <QMap>
+#include <QStringList>
 
 class HWNetServer;
 class QTcpSocket;
@@ -49,7 +50,7 @@
   class ShouldDisconnectException {};
 
   QString client_nick;
-  void ParseLine(const QByteArray & line);
+  void ParseCmd(const QStringList & lst);
   unsigned int removeTeam(const QString& tname); // returns netID
 
   HWNetServer* m_hwserver;
@@ -58,7 +59,7 @@
   void RawSendNet(const QString & buf);
   void RawSendNet(const QByteArray & buf);
 
-  //QByteArray readbuffer;
+  QStringList cmdbuf;
 
  signals:
   void HWClientDisconnected(HWConnectedClient* client);
--- a/QTfrontend/newnetclient.cpp	Tue Jul 15 16:40:50 2008 +0000
+++ b/QTfrontend/newnetclient.cpp	Mon Jul 21 09:45:40 2008 +0000
@@ -26,7 +26,7 @@
 #include "gamecfgwidget.h"
 #include "teamselect.h"
 
-char delimeter=0x17;
+char delimeter='\n';
 
 HWNewNet::HWNewNet(GameUIConfig * config, GameCFGWidget* pGameCFGWidget, TeamSelWidget* pTeamSelWidget) :
   config(config),
@@ -54,9 +54,14 @@
   NetSocket.disconnectFromHost();
 }
 
-void HWNewNet::JoinGame(const QString & game)
+void HWNewNet::CreateRoom(const QString & room)
 {
-  RawSendNet(QString("JOIN%1%2").arg(delimeter).arg(game));
+	RawSendNet(QString("CREATE%1%2").arg(delimeter).arg(room));
+}
+
+void HWNewNet::JoinRoom(const QString & room)
+{
+	RawSendNet(QString("JOIN%1%2").arg(delimeter).arg(room));
 }
 
 void HWNewNet::AddTeam(const HWTeam & team)
@@ -87,7 +92,6 @@
 {
   QString msg = QString(buf.toBase64());
 
-  //NetBuffer += buf;
   RawSendNet(QString("GAMEMSG:%1%2").arg(delimeter).arg(msg));
 }
 
@@ -100,19 +104,27 @@
 {
 qDebug() << "Client: " << buf;
   NetSocket.write(buf);
-  NetSocket.write("\n", 1);
+  NetSocket.write("\n\n", 2);
 }
 
 void HWNewNet::ClientRead()
 {
-  while (NetSocket.canReadLine()) {
-    ParseLine(NetSocket.readLine().trimmed());
-  }
+	while (NetSocket.canReadLine()) {
+		QString s = QString::fromUtf8(NetSocket.readLine().trimmed());
+
+		if (s.size() == 0) {
+			ParseCmd(cmdbuf);
+			cmdbuf.clear();
+		} else
+			cmdbuf << s;
+	}
 }
 
 void HWNewNet::OnConnect()
 {
   RawSendNet(QString("NICK%1%2").arg(delimeter).arg(mynick));
+  RawSendNet(QString("PROTO%1%2").arg(delimeter).arg(*cProtoVer));
+  RawSendNet(QString("CREATE%1%2").arg(delimeter).arg("myroom"));
 }
 
 void HWNewNet::OnDisconnect()
@@ -141,13 +153,16 @@
   }
 }
 
-void HWNewNet::ParseLine(const QByteArray & line)
+void HWNewNet::ParseCmd(const QStringList & lst)
 {
-qDebug() << "Server: " << line;
-  QString msg = QString::fromUtf8 (line.data(), line.size());
+qDebug() << "Server: " << lst;
 
-  QStringList lst = msg.split(delimeter);
-//qDebug() << "Parsing: " << lst;
+  if(!lst.size())
+  {
+    qWarning("Net client: Bad message");
+    return;
+  }
+
   if (lst[0] == "ERRONEUSNICKNAME") {
     QMessageBox::information(0, 0, "Your net nickname is in use or cannot be used");
     return;
@@ -161,13 +176,14 @@
   }
 
   if (lst[0] == "CHAT_STRING") {
-    lst.pop_front();
-    if(lst.size() < 2)
+    if(lst.size() < 3)
     {
 	  qWarning("Net: Empty CHAT_STRING message");
 	  return;
     }
-    emit chatStringFromNet(lst);
+    QStringList tmp = lst;
+    tmp.removeFirst();
+    emit chatStringFromNet(tmp);
     return;
   }
 
@@ -177,8 +193,9 @@
 	  qWarning("Net: Too short ADDTEAM message");
 	  return;
     }
-    lst.pop_front();
-    emit AddNetTeam(lst);
+    QStringList tmp = lst;
+    tmp.removeFirst();
+    emit AddNetTeam(tmp);
     return;
   }
 
@@ -230,18 +247,19 @@
   }
 
   if (lst[0] == "CONFIGURED") {
-    lst.pop_front();
-    if(lst.size() < 6)
+    QStringList tmp = lst;
+    tmp.removeFirst();
+    if(tmp.size() < 6)
     {
       qWarning("Net: Bad CONFIGURED message");
       return;
     }
-    emit seedChanged(lst[0]);
-    emit mapChanged(lst[1]);
-    emit themeChanged(lst[2]);
-    emit initHealthChanged(lst[3].toUInt());
-    emit turnTimeChanged(lst[4].toUInt());
-    emit fortsModeChanged(lst[5].toInt() != 0);
+    emit seedChanged(tmp[0]);
+    emit mapChanged(tmp[1]);
+    emit themeChanged(tmp[2]);
+    emit initHealthChanged(tmp[3].toUInt());
+    emit turnTimeChanged(tmp[4].toUInt());
+    emit fortsModeChanged(tmp[5].toInt() != 0);
     return;
   }
 
@@ -310,7 +328,7 @@
 	  emit hhnumChanged(tmptm);
 	  return;
   	}
-    qWarning(QString("Net: Unknown 'CONFIG_PARAM' message: '%1'").arg(msg).toAscii().data());
+    qWarning() << "Net: Unknown 'CONFIG_PARAM' message:" << lst;
     return;
   }
 
@@ -328,7 +346,7 @@
     return;
   }
 
-  qWarning(QString("Net: Unknown message: '%1'").arg(msg).toAscii().data());
+  qWarning() << "Net: Unknown message:" << lst;
 }
 
 
--- a/QTfrontend/newnetclient.h	Tue Jul 15 16:40:50 2008 +0000
+++ b/QTfrontend/newnetclient.h	Mon Jul 21 09:45:40 2008 +0000
@@ -41,7 +41,8 @@
   HWNewNet(GameUIConfig * config, GameCFGWidget* pGameCFGWidget, TeamSelWidget* pTeamSelWidget);
   void Connect(const QString & hostName, quint16 port, const QString & nick);
   void Disconnect();
-  void JoinGame(const QString & game);
+  void JoinRoom(const QString & room);
+  void CreateRoom(const QString & room);
   void StartGame();
 
  private:
@@ -79,9 +80,11 @@
     emit FromNet(enginemsg);
   }
 
+  QStringList cmdbuf;
+
   void RawSendNet(const QString & buf);
   void RawSendNet(const QByteArray & buf);
-  void ParseLine(const QByteArray & line);
+  void ParseCmd(const QStringList & lst);
 
  signals:
   void AskForRunGame();
--- a/netserver/HWProto.hs	Tue Jul 15 16:40:50 2008 +0000
+++ b/netserver/HWProto.hs	Mon Jul 21 09:45:40 2008 +0000
@@ -6,94 +6,85 @@
 import Miscutils
 import Maybe (fromMaybe, fromJust)
 
--- 'noInfo' clients state command handlers
-handleCmd_noInfo :: Handle -> [ClientInfo] -> [RoomInfo] -> [String] -> ([ClientInfo], [RoomInfo], [Handle], [String])
+-- Main state-independent cmd handler
+handleCmd :: CmdHandler
+handleCmd client _ rooms ("QUIT":xs) =
+	if null (room client) then
+		(noChangeClients, noChangeRooms, clientOnly, ["QUIT"])
+	else if isMaster client then
+		(noChangeClients, removeRoom (room client), sameRoom, ["ROOMABANDONED"]) -- core disconnects clients on ROOMABANDONED command
+	else
+		(noChangeClients, noChangeRooms, sameRoom, ["QUIT", nick client])
 
-handleCmd_noInfo clhandle clients rooms ("NICK":newNick:[]) =
+-- check state and call state-dependent commmand handlers
+handleCmd client clients rooms cmd =
+	if null (nick client) || protocol client == 0 then
+		handleCmd_noInfo client clients rooms cmd
+	else if null (room client) then
+		handleCmd_noRoom client clients rooms cmd
+	else
+		handleCmd_inRoom client clients rooms cmd
+
+-- 'no info' state - need to get protocol number and nickname
+handleCmd_noInfo :: CmdHandler
+handleCmd_noInfo client clients _ ["NICK", newNick] =
 	if not . null $ nick client then
-		(clients, rooms, [clhandle], ["ERROR", "The nick already chosen"])
+		(noChangeClients, noChangeRooms, clientOnly, ["ERROR", "The nick already chosen"])
 	else if haveSameNick then
-		(clients, rooms, [clhandle], ["WARNING", "Choose another nick"])
+		(noChangeClients, noChangeRooms, clientOnly, ["WARNING", "Choose another nick"])
 	else
-		(modifyClient clhandle clients (\cl -> cl{nick = newNick}), rooms, [clhandle], ["NICK", newNick])
+		(modifyClient client{nick = newNick}, noChangeRooms, clientOnly, ["NICK", newNick])
 	where
 		haveSameNick = not . null $ filter (\cl -> newNick == nick cl) clients
-		client = clientByHandle clhandle clients
 
-handleCmd_noInfo clhandle clients rooms ("PROTO":protoNum:[]) =
+handleCmd_noInfo client _ _ ["PROTO", protoNum] =
 	if protocol client > 0 then
-		(clients, rooms, [clhandle], ["ERROR", "Protocol number already known"])
+		(noChangeClients, noChangeRooms, clientOnly, ["ERROR", "Protocol number already known"])
 	else if parsedProto == 0 then
-		(clients, rooms, [clhandle], ["ERROR", "Bad input"])
+		(noChangeClients, noChangeRooms, clientOnly, ["ERROR", "Bad input"])
 	else
-		(modifyClient clhandle clients (\cl -> cl{protocol = parsedProto}), rooms, [], [])
+		(modifyClient client{protocol = parsedProto}, noChangeRooms, clientOnly, ["PROTO", show parsedProto])
 	where
 		parsedProto = fromMaybe 0 (maybeRead protoNum :: Maybe Word16)
-		client = clientByHandle clhandle clients
 
-handleCmd_noInfo clhandle clients rooms _ = (clients, rooms, [clhandle], ["ERROR", "Bad command or incorrect parameter"])
-
+handleCmd_noInfo _ _ _ _ = (noChangeClients, noChangeRooms, clientOnly, badCmd)
 
 -- 'noRoom' clients state command handlers
-handleCmd_noRoom :: Handle -> [ClientInfo] -> [RoomInfo] -> [String] -> ([ClientInfo], [RoomInfo], [Handle], [String])
-
-handleCmd_noRoom clhandle clients rooms ("LIST":[]) =
-		(clients, rooms, [clhandle], ["ROOMS"] ++ map (\r -> name r) rooms)
+handleCmd_noRoom :: CmdHandler
+handleCmd_noRoom client _ rooms ["LIST"] =
+		(noChangeClients, noChangeRooms, clientOnly, ["ROOMS"] ++ map name rooms)
 
-handleCmd_noRoom clhandle clients rooms ("CREATE":newRoom:roomPassword:[]) =
+handleCmd_noRoom client _ rooms ["CREATE", newRoom, roomPassword] =
 	if haveSameRoom then
-		(clients, rooms, [clhandle], ["WARNING", "There's already a room with that name"])
+		(noChangeClients, noChangeRooms, clientOnly, ["WARNING", "There's already a room with that name"])
 	else
-		(modifyClient clhandle clients (\cl -> cl{room = newRoom, isMaster = True}), (RoomInfo newRoom roomPassword):rooms, [clhandle], ["JOINS", nick client])
+		(modifyClient client{room = newRoom, isMaster = True}, addRoom (RoomInfo newRoom roomPassword), clientOnly, ["JOINED", nick client])
 	where
 		haveSameRoom = not . null $ filter (\room -> newRoom == name room) rooms
-		client = clientByHandle clhandle clients
 
-handleCmd_noRoom clhandle clients rooms ("CREATE":newRoom:[]) =
-	handleCmd_noRoom clhandle clients rooms ["CREATE", newRoom, ""]
-
-handleCmd_noRoom clhandle clients rooms ("JOIN":roomName:roomPassword:[]) =
+handleCmd_noRoom client clients rooms ["CREATE", newRoom] =
+	handleCmd_noRoom client clients rooms ["CREATE", newRoom, ""]
+	
+handleCmd_noRoom client _ rooms ["JOIN", roomName, roomPassword] =
 	if noSuchRoom then
-		(clients, rooms, [clhandle], ["WARNING", "There's no room with that name"])
+		(noChangeClients, noChangeRooms, clientOnly, ["WARNING", "There's no room with that name"])
 	else if roomPassword /= password (roomByName roomName rooms) then
-		(clients, rooms, [clhandle], ["WARNING", "Wrong password"])
+		(noChangeClients, noChangeRooms, clientOnly, ["WARNING", "Wrong password"])
 	else
-		(modifyClient clhandle clients (\cl -> cl{room = roomName}), rooms, clhandle : (fromRoomHandles roomName clients), ["JOINS", nick client])
+		(modifyClient client{room = roomName}, noChangeRooms, fromRoom roomName, ["JOINED", nick client])
 	where
 		noSuchRoom = null $ filter (\room -> roomName == name room) rooms
-		client = clientByHandle clhandle clients
 
-handleCmd_noRoom clhandle clients rooms ("JOIN":roomName:[]) =
-	handleCmd_noRoom clhandle clients rooms ["JOIN", roomName, ""]
+handleCmd_noRoom client clients rooms ["JOIN", roomName] =
+	handleCmd_noRoom client clients rooms ["JOIN", roomName, ""]
 
-handleCmd_noRoom clhandle clients rooms _ = (clients, rooms, [clhandle], ["ERROR", "Bad command or incorrect parameter"])
+handleCmd_noRoom _ _ _ _ = (noChangeClients, noChangeRooms, clientOnly, badCmd)
 
 -- 'inRoom' clients state command handlers
-handleCmd_inRoom :: Handle -> [ClientInfo] -> [RoomInfo] -> [String] -> ([ClientInfo], [RoomInfo], [Handle], [String])
+handleCmd_inRoom :: CmdHandler
 
-handleCmd_inRoom clhandle clients rooms _ = (clients, rooms, [clhandle], ["ERROR", "Bad command or incorrect parameter"])
-
--- state-independent command handlers
-handleCmd :: Handle -> [ClientInfo] -> [RoomInfo] -> [String] -> ([ClientInfo], [RoomInfo], [Handle], [String])
+handleCmd_inRoom client _ _ ["CHAT_STRING", _, msg] = (noChangeClients, noChangeRooms, othersInRoom, ["CHAT_STRING", nick client, msg])
 
-handleCmd clhandle clients rooms ("QUIT":xs) =
-	if null (room client) then
-		(clients, rooms, [clhandle], ["QUIT"])
-	else if isMaster client then
-		(clients, filter (\rm -> room client /= name rm) rooms, roomMates, ["ROOMABANDONED"]) -- core disconnects clients on ROOMABANDONED command
-	else
-		(clients, rooms, roomMates, ["QUIT", nick client])
-	where
-		client = clientByHandle clhandle clients
-		roomMates = fromRoomHandles (room client) clients
+handleCmd_inRoom client clients rooms ["CONFIG_PARAM", paramName, value] = (noChangeClients, noChangeRooms, othersInRoom, ["CONFIG_PARAM", paramName, value])
 
--- check state and call state-dependent commmand handlers
-handleCmd clhandle clients rooms cmd =
-	if null (nick client) || protocol client == 0 then
-		handleCmd_noInfo clhandle clients rooms cmd
-	else if null (room client) then
-		handleCmd_noRoom clhandle clients rooms cmd
-	else
-		handleCmd_inRoom clhandle clients rooms cmd
-	where
-		client = clientByHandle clhandle clients
+handleCmd_inRoom _ _ _ _ = (noChangeClients, noChangeRooms, clientOnly, badCmd)
--- a/netserver/Miscutils.hs	Tue Jul 15 16:40:50 2008 +0000
+++ b/netserver/Miscutils.hs	Mon Jul 21 09:45:40 2008 +0000
@@ -9,9 +9,9 @@
 
 
 data ClientInfo =
-	ClientInfo
+ ClientInfo
 	{
-		chan :: TChan String,
+		chan :: TChan [String],
 		handle :: Handle,
 		nick :: String,
 		protocol :: Word16,
@@ -19,6 +19,9 @@
 		isMaster :: Bool
 	}
 
+instance Eq ClientInfo where
+	a1 == a2 = handle a1 == handle a2
+
 data RoomInfo =
 	RoomInfo
 	{
@@ -26,24 +29,17 @@
 		password :: String
 	}
 
-clientByHandle :: Handle -> [ClientInfo] -> ClientInfo
-clientByHandle clhandle clients = fromJust $ find (\ci -> handle ci == clhandle) clients
+type ClientsTransform = [ClientInfo] -> [ClientInfo]
+type RoomsTransform = [RoomInfo] -> [RoomInfo]
+type HandlesSelector = ClientInfo -> [ClientInfo] -> [RoomInfo] -> [Handle]
+type CmdHandler = ClientInfo -> [ClientInfo] -> [RoomInfo] -> [String] -> (ClientsTransform, RoomsTransform, HandlesSelector, [String])
+
 
 roomByName :: String -> [RoomInfo] -> RoomInfo
 roomByName roomName rooms = fromJust $ find (\room -> roomName == name room) rooms
 
-fromRoomHandles :: String -> [ClientInfo] -> [Handle]
-fromRoomHandles roomName clients = map (\ci -> handle ci) $ filter (\ci -> room ci == roomName) clients
-
-modifyClient :: Handle -> [ClientInfo] -> (ClientInfo -> ClientInfo) -> [ClientInfo]
-modifyClient clhandle (cl:cls) func =
-	if handle cl == clhandle then
-		(func cl) : cls
-	else
-		cl : (modifyClient clhandle cls func)
-
-tselect :: [ClientInfo] -> STM (String, Handle)
-tselect = foldl orElse retry . map (\ci -> (flip (,) $ handle ci) `fmap` readTChan (chan ci))
+tselect :: [ClientInfo] -> STM ([String], ClientInfo)
+tselect = foldl orElse retry . map (\ci -> (flip (,) ci) `fmap` readTChan (chan ci))
 
 maybeRead :: Read a => String -> Maybe a
 maybeRead s = case reads s of
@@ -56,3 +52,37 @@
 
 deleteFirstsBy2t :: (a -> b -> Bool) -> [a] -> [b] -> [a]
 deleteFirstsBy2t eq =  foldl (flip (deleteBy2t eq))
+
+sameRoom :: HandlesSelector
+sameRoom client clients rooms = map handle $ filter (\ci -> room ci == room client) clients
+
+othersInRoom :: HandlesSelector
+othersInRoom client clients rooms = map handle $ filter (client /=) $ filter (\ci -> room ci == room client) clients
+
+fromRoom :: String -> HandlesSelector
+fromRoom roomName _ clients _ = map handle $ filter (\ci -> room ci == roomName) clients
+
+clientOnly :: HandlesSelector
+clientOnly client _ _ = [handle client]
+
+noChangeClients :: ClientsTransform
+noChangeClients a = a
+
+modifyClient :: ClientInfo -> ClientsTransform
+modifyClient client (cl:cls) =
+	if cl == client then
+		client : cls
+	else
+		cl : (modifyClient client cls)
+
+noChangeRooms :: RoomsTransform
+noChangeRooms a = a
+
+addRoom :: RoomInfo -> RoomsTransform
+addRoom room rooms = room:rooms
+
+removeRoom :: String -> RoomsTransform
+removeRoom roomname rooms = filter (\rm -> roomname /= name rm) rooms
+
+badCmd :: [String]
+badCmd = ["ERROR", "Bad command, state or incorrect parameter"]
--- a/netserver/newhwserv.hs	Tue Jul 15 16:40:50 2008 +0000
+++ b/netserver/newhwserv.hs	Mon Jul 21 09:45:40 2008 +0000
@@ -17,20 +17,23 @@
 	cChan <- atomically newTChan
 	forkIO $ clientLoop cHandle cChan
 	atomically $ writeTChan acceptChan (ClientInfo cChan cHandle "" 0 "" False)
-	hPutStrLn cHandle "CONNECTED"
+	hPutStrLn cHandle "CONNECTED\n"
 	acceptLoop servSock acceptChan
 
-listenLoop :: Handle -> TChan String -> IO ()
-listenLoop handle chan = do
+listenLoop :: Handle -> [String] -> TChan [String] -> IO ()
+listenLoop handle buf chan = do
 	str <- hGetLine handle
-	atomically $ writeTChan chan str
-	listenLoop handle chan
+	if str == "" then do
+		atomically $ writeTChan chan buf
+		listenLoop handle [] chan
+		else
+		listenLoop handle (buf ++ [str]) chan
 
-clientLoop :: Handle -> TChan String -> IO ()
+clientLoop :: Handle -> TChan [String] -> IO ()
 clientLoop handle chan =
-	listenLoop handle chan
+	listenLoop handle [] chan
 		`catch` (const $ clientOff >> return ())
-	where clientOff = atomically $ writeTChan chan "QUIT"
+	where clientOff = atomically $ writeTChan chan ["QUIT"]
 
 mainLoop :: Socket -> TChan ClientInfo -> [ClientInfo] -> [RoomInfo] -> IO ()
 mainLoop servSock acceptChan clients rooms = do
@@ -38,17 +41,24 @@
 	case r of
 		Left ci -> do
 			mainLoop servSock acceptChan (ci:clients) rooms
-		Right (line, clhandle) -> do
-			let (mclients, mrooms, recipients, strs) = handleCmd clhandle clients rooms $ words line
+		Right (cmd, client) -> do
+			print ("> " ++ show cmd)
+			let (clientsFunc, roomsFunc, handlesFunc, answer) = handleCmd client clients rooms $ cmd
+			print ("< " ++ show answer)
 
+			let mclients = clientsFunc clients
+			let mrooms = roomsFunc rooms
+			let recipients = handlesFunc client clients rooms
+			
 			clHandles' <- forM recipients $
 					\ch -> do
-							forM_ strs (\str -> hPutStrLn ch str)
+							forM_ answer (\str -> hPutStrLn ch str)
+							hPutStrLn ch ""
 							hFlush ch
-							if (not $ null strs) && (head strs == "ROOMABANDONED") then hClose ch >> return [ch] else return []
+							if (not $ null answer) && (head answer == "ROOMABANDONED") then hClose ch >> return [ch] else return []
 					`catch` const (hClose ch >> return [ch])
 
-			clHandle' <- if (not $ null strs) && (head strs == "QUIT") then hClose clhandle >> return [clhandle] else return []
+			clHandle' <- if (not $ null answer) && (head answer == "QUIT") then hClose (handle client) >> return [handle client] else return []
 
 			mainLoop servSock acceptChan (remove (remove mclients (concat clHandles')) clHandle') mrooms
 			where