Older messages will now be purged when a user joins the room
Older messages will now be purged when a user joins the room

The aim of this is to prevent the new user from dumping out the entire stored history of a chat. Although the client will honour "last" they could conceivably override it to 0 and grab everything.

So, when they join, we now remove any messages older than 10 seconds.

At the moment, it's a little meaningless as you don't need to join in order to do pollMsg. That will obviously need to change.

--- a/client/LocalChatClient.py
+++ b/client/LocalChatClient.py
@@ -15,6 +15,9 @@
 import json
 import urllib2
 
+import datetime as dt
+
+
 # We'll get these from the commandline later
 USER='ben2'
 SERVER='http://127.0.0.1:8090'
@@ -25,9 +28,9 @@
     
     def __init__(self):
         self.last = 0
-        self.user = USER
+        self.user = False
         self.server = SERVER
-        self.room = ROOMNAME
+        self.room = False
     
     
     def pollForMessage(self):
@@ -59,14 +62,105 @@
         # Otherwise, process the messages
         for i in resp["messages"]:
             self.last = i[0]
-            msgbody = self.decrypt(i[1])
+            msgbody = json.loads(self.decrypt(i[1]))
             
             # TODO - We'll need to json decode and extract the sending user's name
-            # but not currently including that info in my curl tests
-            
-            to_print.append(msgbody)
+            # but not currently including that info in my curl tests. Also means test that part of the next block
+            
+            color = "green"
+            upstruser = msgbody['user'] # Temporary placeholder
+            
+            if upstruser == USER:
+                # One of our own, change the color
+                color = "blue"
+            
+            elif upstruser == "SYSTEM":
+                color = "magenta"
+                
+            
+            ts = dt.datetime.utcfromtimestamp(i[2]).strftime("[%H:%M:%S]")
+            
+            line = [
+                ts, # timestamp
+                "%s>" % (upstruser,), # To be replaced later
+                msgbody['text']
+                ]
+            
+            to_print.append([color,' '.join(line)])
         
         return to_print
+        
+        
+    def sendMsg(self,line):
+        ''' Send a message 
+        '''
+        
+        if not self.room:
+            # We're not in a room. Abort
+            return False
+        
+        # Otherwise, build a payload
+        
+        
+        msg = {
+            'user': self.user,
+            'text': line
+            }
+        
+        payload = {"roomName": self.room, 
+                   "msg":self.encrypt(json.dumps(msg)),
+                   "user": self.user
+                   }
+        
+        request = {"action":"sendMsg",
+                   "payload": json.dumps(payload)
+                   }
+
+        resp = self.sendRequest(request)        
+        
+        if resp['status'] == "ok":
+            return True
+        
+        return False
+        
+
+
+    def joinRoom(self,user,room,passw):
+        ''' Join a room
+        '''
+        
+        # TODO - this functionality isn't on the 
+        # backend yet, so haven't defined the hashing mechanism etc
+        passhash = passw
+        
+        payload = {"roomName": room, 
+                   "passhash": passhash,
+                   "user": user
+                   }
+        
+        request = {"action":"joinRoom",
+                   "payload": json.dumps(payload)
+                   }        
+
+
+        resp = self.sendRequest(request)
+
+        if resp == "BROKENLINK" or resp['status'] != "ok":
+            return False
+        
+        
+        self.room = room
+        self.user = user
+        self.last = resp['last']
+        return True
+
+
+
+
+    def leaveRoom(self):
+        ''' Placeholder '''
+        if not self.room:
+            return False
         
         
 
@@ -91,12 +185,27 @@
         return msg
     
 
-
-
-
-class UnknownCommand(Exception):
+    def encrypt(self,msg):
+        ''' Placeholder
+        '''
+        return msg
+
+
+
+class NotInRoom(Exception):
     def __init__(self,cmd):
-        Exception.__init__(self,'Uknown command: %s'%cmd)
+        Exception.__init__(self,'Message not sent')
+
+
+class UnableToJoin(Exception):
+    def __init__(self,cmd):
+        Exception.__init__(self,'Could not Join: %s' % (cmd,))
+
+
+class InvalidCommand(Exception):
+    def __init__(self,cmd):
+        Exception.__init__(self,'Command is invalid: %s' % (cmd,))
+
 
 class Command(object):
     """ Base class to manage commands in commander
@@ -108,9 +217,27 @@
         self._help_cmd=help_commands
         
     def __call__(self,line):
+        global msg
         tokens=line.split()
         cmd=tokens[0].lower()
         args=tokens[1:]
+        
+        if cmd[0] == "/":
+            # It's a command
+            cmd = cmd[1:]
+            
+            if cmd == "join":
+                # /join [username] [room] [password]
+                
+                if len(args) < 3:
+                    raise InvalidCommand(line)
+                
+                if not msg.joinRoom(args[0],args[1],args[2]):
+                    raise UnableToJoin(line)
+                return
+            
+        
+
         if cmd in self._quit_cmd:
             return Commander.Exit
         elif cmd in self._help_cmd:
@@ -118,7 +245,11 @@
         elif hasattr(self, 'do_'+cmd):
             return getattr(self, 'do_'+cmd)(*args)
         else:
-            raise UnknownCommand(cmd)
+            # If it's not a command, then we're trying to send a message
+            r = msg.sendMsg(line)
+            if not r:
+                raise NotInRoom(line)
+            
         
     def help(self,cmd=None):
         def std_help():
@@ -310,10 +441,11 @@
             
             if m == "BROKENLINK":
                 c.output("Server went away", 'Red')
+                continue
                 
             if m:
                 for i in m:
-                    c.output(i, 'green')
+                    c.output(i[1], i[0])
                 
     t=Thread(target=run)
     t.daemon=True

--- a/server/LocalChat.py
+++ b/server/LocalChat.py
@@ -116,6 +116,9 @@
         if reqjson['action'] == "createRoom":
             return self.createRoom(reqjson)
         
+        elif reqjson['action'] == "joinRoom":
+            return self.processjoinRoom(reqjson)
+        
         elif reqjson['action'] == "inviteUser":
             return self.inviteUser(reqjson)
         
@@ -210,6 +213,65 @@
         
     
     
+    def processjoinRoom(self,reqjson):
+        ''' Process a request from a user to login to a room
+        
+        Not yet defined the authentication mechanism to use, so that's a TODO
+        '''
+        if "roomName" not in reqjson['payload'] or "user" not in reqjson['payload']:
+            return 400
+        
+        room = self.getRoomID(reqjson['payload']["roomName"])
+        
+        if not room:
+            return 400
+        
+        
+        # Check whether that user is authorised to connect to that room
+        self.cursor.execute("SELECT username, room from users where username=? and room=?",(reqjson['payload']['user'],room))
+        r = self.cursor.fetchone()
+        
+        if not r:
+            return { "status": "NOK" }
+        else:
+            
+            
+            # Tidy older messages away.
+            #
+            # We do this so that a user who joins can't then send a poll with last:0 to retrieve the full history
+            #
+            # Basically, anything older than 10 seconds should go. Users who were already present will be able
+            # to scroll up and down in their client anyway
+            self.tidyMsgs(time.time()-10,room)
+            
+            
+            # Push a message to the room to note that the user joined
+            
+            m = {
+                    "user":"SYSTEM",
+                    "text":"User %s joined the room" % (reqjson['payload']['user'])
+                }
+            
+            self.cursor.execute("INSERT INTO messages (ts,room,msg) VALUES (?,?,?)",(time.time(),room,json.dumps(m)))
+            msgid = self.cursor.lastrowid
+            self.conn.commit()
+            
+            # Check the latest message ID for that room
+            self.cursor.execute("SELECT id from messages WHERE room=? and id != ? ORDER BY id DESC",(room,msgid))
+            r = self.cursor.fetchone()
+            
+            if not r:
+                last = 0
+            else:
+                last = r[0]
+                   
+            
+            return {"status":"ok","last":last}
+        
+        
+        
+    
+    
     def sendMsg(self,reqjson):
         ''' Push a message into a room
         
@@ -268,7 +330,7 @@
         if not room:
             return 400        
         
-        self.cursor.execute("""SELECT id,msg FROM messages
+        self.cursor.execute("""SELECT id,msg,ts FROM messages
             WHERE room=? AND
             id > ?
             ORDER BY ts ASC           
@@ -312,6 +374,20 @@
         return r[0]
     
 
+    def tidyMsgs(self,thresholdtime,room=False):
+        ''' Remove messages older than the threshold time
+        '''
+        
+        if room:
+            # Tidy from a specific room
+            self.cursor.execute("DELETE FROM messages where ts < ? and room = ?",(thresholdtime,room))
+            self.conn.commit()
+            
+        else:
+            self.cursor.execute("DELETE FROM messages where ts < ?",(thresholdtime,))
+            self.conn.commit()
+
+
 
     def test(self):
         return ['foo']