Do not re-use message IDs
Do not re-use message IDs

Without this, polling breaks as we look for IDs > our last. When key re-use is permitted the latest ID may well be less than the most recent one we've seen

--- a/client/LocalChatClient.py
+++ b/client/LocalChatClient.py
@@ -70,7 +70,7 @@
             color = "green"
             upstruser = msgbody['user'] # Temporary placeholder
             
-            if upstruser == USER:
+            if upstruser == self.user:
                 # One of our own, change the color
                 color = "blue"
             
@@ -158,11 +158,31 @@
 
 
     def leaveRoom(self):
-        ''' Placeholder '''
+        ''' Leave the current room
+        '''
         if not self.room:
             return False
         
-        
+        payload = {"roomName": self.room, 
+                   "user": self.user
+                   }
+        
+        request = {"action":"leaveRoom",
+                   "payload": json.dumps(payload)
+                   }        
+
+
+        resp = self.sendRequest(request)
+
+        if resp == "BROKENLINK" or resp['status'] != "ok":
+            return False
+        
+        self.room = False
+        self.user = False
+        self.last = 0
+        
+        return True
+                
 
 
     def sendRequest(self,data):
@@ -197,9 +217,9 @@
         Exception.__init__(self,'Message not sent')
 
 
-class UnableToJoin(Exception):
-    def __init__(self,cmd):
-        Exception.__init__(self,'Could not Join: %s' % (cmd,))
+class UnableTo(Exception):
+    def __init__(self,action,cmd):
+        Exception.__init__(self,'Could not %s: %s' % (action,cmd))
 
 
 class InvalidCommand(Exception):
@@ -233,9 +253,17 @@
                     raise InvalidCommand(line)
                 
                 if not msg.joinRoom(args[0],args[1],args[2]):
-                    raise UnableToJoin(line)
+                    raise UnableTo('join',line)
                 return
             
+            if cmd == "leave":
+                # /leave
+                if not msg.leaveRoom():
+                    raise UnableTo('leave',line)
+                
+                global c
+                c.output('Left the room','magenta')
+                return
         
 
         if cmd in self._quit_cmd:
@@ -434,16 +462,20 @@
     #Test asynch output -  e.g. comming from different thread
     import time
     def run():
+        state=1
         while True:
             time.sleep(1)
             
             m = msg.pollForMessage()
             
             if m == "BROKENLINK":
-                c.output("Server went away", 'Red')
+                if state == 1:
+                    c.output("Server went away", 'Red')
+                    state = 0
                 continue
                 
             if m:
+                state = 1
                 for i in m:
                     c.output(i[1], i[0])
                 

--- a/server/LocalChat.py
+++ b/server/LocalChat.py
@@ -71,7 +71,7 @@
         
         
         CREATE TABLE messages (
-            id INTEGER PRIMARY KEY,
+            id INTEGER PRIMARY KEY AUTOINCREMENT,
             ts INTEGER NOT NULL,
             room INTEGER NOT NULL,
             msg TEXT NOT NULL
@@ -81,6 +81,7 @@
         CREATE TABLE users (
             username TEXT NOT NULL,
             room INTEGER NOT NULL,
+            active INTEGER DEFAULT 0,
             PRIMARY KEY (username,room)
         );
         
@@ -118,6 +119,9 @@
         
         elif reqjson['action'] == "joinRoom":
             return self.processjoinRoom(reqjson)
+        
+        elif reqjson['action'] == "leaveRoom":
+            return self.processleaveRoom(reqjson)
         
         elif reqjson['action'] == "inviteUser":
             return self.inviteUser(reqjson)
@@ -265,11 +269,44 @@
             else:
                 last = r[0]
                    
+            # Mark the user as active in the users table
+            self.cursor.execute("UPDATE users set active=1 where username=? and room=?", (reqjson['payload']['user'],room))
+            self.conn.commit()
+            
             
             return {"status":"ok","last":last}
         
         
-        
+    def processleaveRoom(self,reqjson):
+        ''' Process a user's request to leave a room
+        '''
+        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 the user is actually in the room and authorised
+        if not self.validateUser(reqjson['payload']):
+            return 400
+        
+        # Mark them as not in the room
+        self.cursor.execute("UPDATE users set active=0 where username=? and room=?", (reqjson['payload']['user'],room))
+        self.conn.commit()
+        
+        # Push a message to the room to note they left
+        m = {
+                "user":"SYSTEM",
+                "text":"User %s left 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()        
+        
+        return {"status":"ok"}
     
     
     def sendMsg(self,reqjson):
@@ -353,13 +390,28 @@
         
     
     def validateUser(self,payload):
-        ''' Placeholder for now. Auth will be handled later
-        '''
-        if "user" not in payload:
+        ''' Placeholder for now. Auth will be handled in more depth later
+        '''
+        if "user" not in payload or "roomName" not in payload:
             return False
         
+        
+        room = self.getRoomID(payload["roomName"])
+        if not room:
+            return 400        
+        
+        
+        
+        # Check whether the user has been marked as active
+        self.cursor.execute("SELECT username, room from users where username=? and room=? and active=1",(payload['user'],room))
+        r = self.cursor.fetchone()
+        
+        if not r:
+            return False
+        
         return True
-        
+
+
     
     def getRoomID(self,roomname):
         ''' Get a room's ID from its name