LOC-6 Initial (and very rough) periodic message clearance
LOC-6 Initial (and very rough) periodic message clearance

At server startup we spin up a seperate scheduler thread, which places a request to the main app every 60 seconds, in order to purge any messages older than the threshold in the system.

It's currently very, very rough, but seems to work.

There's still a bit to be done here though

- Need to validate the submitted password
- Need to actually generate a password rather than using the temporarily hardcoded 1234

And probably quite a lot else too

--- a/client/LocalChatClient.py
+++ b/client/LocalChatClient.py
@@ -39,6 +39,8 @@
         self.server = SERVER
         self.room = False
         self.roompass = False
+        self.sesskey = False
+        self.syskey = False
         self.gpg = gnupg.GPG()
     
     
@@ -51,7 +53,8 @@
         
         payload = {"roomName": self.room, 
                    "mylast":self.last,
-                   "user": self.user
+                   "user": self.user,
+                   "sesskey": self.sesskey
                    }
         
         request = {"action":"pollMsg",
@@ -66,14 +69,23 @@
         
         if resp['status'] == "unchanged":
             return False
+        
+        
+        if resp['status'] == "errmessage":
+            # Got an error, we need to stop the poll and then return the text
+            self.room = False
+            self.roompass = False
+            self.sesskey = False
+            return [['reversed',resp['text']]]
         
         to_print = []
         # Otherwise, process the messages
         for i in resp["messages"]:
             self.last = i[0]
+            upstruser = i[3]
             
             try:
-                msgbody = json.loads(self.decrypt(i[1]))
+                msgbody = json.loads(self.decrypt(i[1],upstruser))
             except:
                 # Means an invalid message was received - LOC-8
                 to_print.append(['error','Received message which could not be decrypted'])
@@ -83,30 +95,44 @@
             # 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 == self.user:
                 # One of our own, change the color
                 color = "blue"
             
             elif upstruser == "SYSTEM":
                 color = "magenta"
+                if msgbody['verb'] == "sysalert":
+                    color = 'reversed'
+                elif msgbody['verb'] == 'syswarn':
+                    color = 'cyan'
                 
             
             ts = dt.datetime.utcfromtimestamp(i[2]).strftime("[%H:%M:%S]")
             
-            line = [
-                ts, # timestamp
-                "%s>" % (upstruser,), # To be replaced later
-                msgbody['text']
-                ]
+            if msgbody["verb"] == "do":
+                color = 'yellow'
+                line = [
+                    "        ** %s %s **" % (upstruser,msgbody['text'])
+                    ]
+            else:
+                
+                if i[4] != "0":
+                    color = 'brown'
+                    upstruser = 'DM %s' % (upstruser,)
+                
+                line = [
+                    ts, # timestamp
+                    "%s>" % (upstruser,), # To be replaced later
+                    msgbody['text']
+                    ]
             
             to_print.append([color,' '.join(line)])
         
         return to_print
         
         
-    def sendMsg(self,line):
+    def sendMsg(self,line,verb='say'):
         ''' Send a message 
         '''
         
@@ -119,12 +145,14 @@
         
         msg = {
             'user': self.user,
-            'text': line
+            'text': line,
+            "verb": verb
             }
         
         payload = {"roomName": self.room, 
                    "msg":self.encrypt(json.dumps(msg)),
-                   "user": self.user
+                   "user": self.user,
+                   "sesskey": self.sesskey
                    }
         
         request = {"action":"sendMsg",
@@ -138,6 +166,43 @@
         
         return False
         
+
+
+    def sendDirectMsg(self,line,user,verb='say'):
+        ''' Send a direct message
+        '''
+        
+        if not self.room:
+            # We're not in a room. Abort
+            return False
+        
+        # Otherwise, build a payload
+        
+        msg = {
+            'user': self.user,
+            'text': line,
+            "verb": verb
+            }
+        
+        payload = {"roomName": self.room, 
+                   "msg":self.encrypt(json.dumps(msg)), # TODO this should use the user's key
+                   "to": user,
+                   "user": self.user,
+                   "sesskey": self.sesskey
+                   }
+        
+        request = {"action":"sendDirectMsg",
+                   "payload": json.dumps(payload)
+                   }
+
+        resp = self.sendRequest(request)        
+        
+        if resp['status'] == "ok":
+            return True
+        
+        return False
+
+
 
 
     def joinRoom(self,user,room,passw):
@@ -168,6 +233,8 @@
         self.user = user
         self.last = resp['last']
         self.roompass = p[0] # The room password is the first section of the password
+        self.sesskey = resp['session']
+        self.syskey = resp['syskey']
         return True
 
 
@@ -180,7 +247,8 @@
             return False
         
         payload = {"roomName": self.room, 
-                   "user": self.user
+                   "user": self.user,
+                   "sesskey": self.sesskey
                    }
         
         request = {"action":"leaveRoom",
@@ -197,6 +265,7 @@
         self.user = False
         self.last = 0
         self.roompass = False
+        self.sesskey = False
         
         return True
                 
@@ -239,6 +308,7 @@
 
         payload = {"roomName": self.room, 
                    "user": self.user,
+                   "sesskey": self.sesskey
                    }
         
         request = {"action":"closeRoom",
@@ -266,7 +336,8 @@
         payload = {"roomName": self.room, 
                    "user": self.user,
                    "invite": user,
-                   "pass": passw
+                   "pass": passw,
+                   "sesskey": self.sesskey
                    }
         
         request = {"action":"inviteUser",
@@ -293,7 +364,8 @@
         
         payload = {"roomName": self.room, 
                    "user": self.user,
-                   "kick": user
+                   "kick": user,
+                   "sesskey": self.sesskey
                    }
         
         request = {"action":action,
@@ -331,17 +403,19 @@
 
 
 
-    def decrypt(self,msg):
+    def decrypt(self,msg,sender):
         ''' Placeholder
         '''
-        
-        try:
-            # Check if it's json as it may be a system message
-            json.loads(msg)
-            return msg
+                
+        try:       
+            key = self.roompass
+            if sender == "SYSTEM":
+                key = self.syskey
+                
+            return str(self.gpg.decrypt(msg.decode("base64"),passphrase=key))
         
         except:
-            return str(self.gpg.decrypt(msg.decode("base64"),passphrase=self.roompass))
+            return False
         
     
 
@@ -402,6 +476,14 @@
             cmd = cmd[1:]
             
             
+            if cmd == "me":
+                #/me [string]
+                r = msg.sendMsg(' '.join(args),'do')
+                if not r:
+                    raise NotInRoom(line)
+                return
+
+            
             if cmd == "ban":
                 # /kick [user]
                 
@@ -445,6 +527,25 @@
                 global c
                 c.output('Left the room','magenta')
                 return
+
+
+            if cmd == 'msg':
+                # /msg ben Hello ben this is a DM
+                line = ' '.join(args[1:])
+                r = msg.sendDirectMsg(line,args[0])
+                if not r:
+                    raise NotInRoom(line)
+                
+                # Otherwise push a copy of the message to display
+                # cos we won't get this one back from pollMsg
+                global c
+                
+                m = "%s DM %s>%s" % (msg.user,args[0],line)
+                
+                c.output(m,'blue')
+                
+                return
+
         
             if cmd == "room":
                 
@@ -522,7 +623,7 @@
             
             
             /room invite [user]                                         Invite a user into the current room
-            
+            /me [string]                                                Send an 'action' instead of a message
             
             Room Admin commands:
             
@@ -620,7 +721,12 @@
               ('error', urwid.LIGHT_RED, urwid.BLACK),
               ('green', urwid.DARK_GREEN, urwid.BLACK),
               ('blue', urwid.LIGHT_BLUE, urwid.BLACK),
-              ('magenta', urwid.DARK_MAGENTA, urwid.BLACK), ]
+              ('magenta', urwid.DARK_MAGENTA, urwid.BLACK), 
+              ('yellow', urwid.YELLOW, urwid.BLACK), 
+              ('cyan', urwid.LIGHT_CYAN, urwid.BLACK), 
+              ('brown', urwid.BROWN, urwid.BLACK), 
+              
+              ]
     
     
     def __init__(self, title, command_caption='Message:  (Tab to switch focus to upper frame, where you can scroll text)', cmd_cb=None, max_size=1000):

--- a/server/LocalChat.py
+++ b/server/LocalChat.py
@@ -13,13 +13,17 @@
 from flask import Flask
 from flask import request, make_response
 
-
-
+import thread
+import urllib2
+import ssl
 import sqlite3
 import time
 import os
 import json
 import bcrypt
+import random
+import string
+import gnupg
 
 app = Flask(__name__)
 
@@ -51,9 +55,13 @@
 class MsgHandler(object):
 
 
-    def __init__(self):
+    def __init__(self,cronpass):
         self.conn = False
         self.cursor = False
+        # Generate a key for encryption of SYSTEM messages (LOC-13)
+        self.syskey = self.genpassw(16)
+        self.gpg = gnupg.GPG()
+        self.cronpass = cronpass
 
 
 
@@ -74,6 +82,8 @@
             id INTEGER PRIMARY KEY AUTOINCREMENT,
             ts INTEGER NOT NULL,
             room INTEGER NOT NULL,
+            user NOT NULL,
+            touser TEXT DEFAULT '0',
             msg TEXT NOT NULL
         );
         
@@ -86,6 +96,22 @@
             PRIMARY KEY (username,room)
         );
         
+        
+        CREATE TABLE sessions (
+            username TEXT NOT NULL,
+            sesskey TEXT NOT NULL,
+            PRIMARY KEY(sesskey)
+        );
+        
+        
+        CREATE TABLE failuremsgs (
+            username TEXT NOT NULL,
+            room INTEGER NOT NULL,
+            expires INTEGER NOT NULL,
+            msg TEXT NOT NULL,
+            PRIMARY KEY (username,room)
+        );
+        
         """
         
         self.conn.executescript(sql)
@@ -103,8 +129,12 @@
         
         
         print reqjson
+        
+        if "action" in reqjson and reqjson['action'] == 'schedulerTrigger':
+            return self.triggerClean(reqjson)
+        
         if "action" not in reqjson or "payload" not in reqjson:
-            return 400
+            return self.returnFailure(400)
         
         
         # Decrypt the payload
@@ -113,7 +143,7 @@
         try:
             reqjson['payload'] = json.loads(reqjson['payload'])
         except:
-            return 400
+            return self.returnFailure(400)
         
         if reqjson['action'] == "createRoom":
             return self.createRoom(reqjson)
@@ -135,6 +165,9 @@
         
         elif reqjson['action'] == "kickUser":
             return self.kickUser(reqjson,False)
+        
+        elif reqjson['action'] == 'sendDirectMsg':
+            return self.sendDirectMsg(reqjson)
         
         elif reqjson['action'] == 'sendMsg':
             return self.sendMsg(reqjson)
@@ -182,7 +215,7 @@
         required = ['roomName','owner','pass']
         for i in required:
             if i not in reqjson['payload']:
-                return 400
+                return self.returnFailure(400)
         
         print "Creating room %s" % (reqjson['payload'])
         
@@ -196,7 +229,7 @@
             roomid = self.cursor.lastrowid
         except:
             # Probably a duplicate name, but we don't want to give the other end a reason anyway
-            return 500
+            return self.returnFailure(500)
         
         
         # Generate a password hash for the owners password
@@ -211,7 +244,7 @@
                 'name' : reqjson['payload']['roomName']
             }
         
-    
+
 
     def closeRoom(self,reqjson):
         ''' Close a room.
@@ -224,12 +257,12 @@
         '''
         
         if "roomName" not in reqjson['payload'] or "user" not in reqjson['payload']:
-            return 400
+            return self.returnFailure(400)
         
         room = self.getRoomID(reqjson['payload']["roomName"])
         
         if not room:
-            return 400
+            return self.returnFailure(400)
         
         
         # Check the requesting user is the admin
@@ -237,18 +270,16 @@
         n = self.cursor.fetchone()
         
         if not n:
-            return 403
-        
-        m = {
-            "user":"SYSTEM",
-            "text":"Room has been closed. Buh-Bye"
-        }
-        self.cursor.execute("INSERT INTO messages (ts,room,msg) VALUES (?,?,?)",(time.time(),room,json.dumps(m))) 
-        self.conn.commit()
-        
+            return self.returnFailure(403)
+        
+
+        self.pushSystemMsg("Room has been closed. Buh-Bye",room,'syswarn')
+                
         self.cursor.execute("DELETE FROM users where room=?",(room,))
         self.cursor.execute("DELETE FROM rooms where id=?",(room,))
         self.cursor.execute("DELETE FROM messages where room=?",(room,))
+        self.cursor.execute("DELETE FROM sessions where sesskey like ?", (reqjson['payload']["roomName"] + '-%',))
+        
         self.conn.commit()
         
         return { "status" : "ok" }
@@ -263,26 +294,21 @@
         '''
         
         if "roomName" not in reqjson['payload'] or "pass" not in reqjson['payload'] or "invite" not in reqjson['payload']:
-            return 400
+            return self.returnFailure(400)
         
         room = self.getRoomID(reqjson['payload']["roomName"])
         
         if not room:
-            return 400
+            return self.returnFailure(400)
         
         if not self.validateUser(reqjson['payload']):
-            return 403
+            return self.returnFailure(403,reqjson['payload'],room)
         
         
         if reqjson['payload']['invite'] == "SYSTEM":
             # Push a notification into the group
-            m = {
-                    "user":"SYSTEM",
-                    "text":"ALERT: User %s tried to invite SYSTEM" % (reqjson['payload']['user'])
-                }
-            self.cursor.execute("INSERT INTO messages (ts,room,msg) VALUES (?,?,?)",(time.time(),room,json.dumps(m))) 
-            self.conn.commit()
-            return 403
+            self.pushSystemMsg("ALERT: User %s tried to invite SYSTEM" % (reqjson['payload']['user']),room,'sysalert')
+            return self.returnFailure(403)
        
         
         # Generate a hash of the submitted password
@@ -292,13 +318,7 @@
         self.cursor.execute("INSERT INTO users (username,room,passhash) values (?,?,?)",(reqjson['payload']['invite'],room,passhash))
         
         # Push a notification into the group
-        m = {
-                "user":"SYSTEM",
-                "text":"User %s invited %s to the room" % (reqjson['payload']['user'],reqjson['payload']['invite'])
-            }
-        
-        self.cursor.execute("INSERT INTO messages (ts,room,msg) VALUES (?,?,?)",(time.time(),room,json.dumps(m)))        
-        self.conn.commit()        
+        self.pushSystemMsg("User %s invited %s to the room" % (reqjson['payload']['user'],reqjson['payload']['invite']),room)
         
         return {
                 "status":'ok'
@@ -313,12 +333,12 @@
         '''
         
         if "roomName" not in reqjson['payload'] or "user" not in reqjson['payload'] or "kick" not in reqjson['payload']:
-            return 400
+            return self.returnFailure(400)
         
         room = self.getRoomID(reqjson['payload']["roomName"])
         
         if not room:
-            return 400
+            return self.returnFailure(400)
         
         
         # Check the requesting user is the admin
@@ -326,35 +346,28 @@
         n = self.cursor.fetchone()
         
         if not n:
-            return 403
+            return self.returnFailure(403)
         
         
         
         self.cursor.execute("UPDATE users set active=0 where room=? and username=?",(room,reqjson["payload"]["kick"]))
-        m = {
-                "user":"SYSTEM",
-                "text":"User %s kicked %s from the room" % (reqjson['payload']['user'],reqjson['payload']['kick'])
-            }
-        
-        self.cursor.execute("INSERT INTO messages (ts,room,msg) VALUES (?,?,?)",(time.time(),room,json.dumps(m)))
-        msgid = self.cursor.lastrowid
-        self.conn.commit()
-                    
+        
+        # Delete their session
+        self.cursor.execute("DELETE FROM sessions where username=? and sesskey like ?", (reqjson['payload']['kick'],reqjson['payload']["roomName"] + '-%'))
+        
+        self.pushSystemMsg("User %s kicked %s from the room" % (reqjson['payload']['user'],reqjson['payload']['kick']),room,'syswarn')
+        
+        self.pushFailureMessage(reqjson['payload']['kick'],room,'You have been kicked from the room')
+        
+        
         if ban:
             # If we're banning them, also need to disinvite them
             self.cursor.execute("DELETE from users where room=? and username=?",(room,reqjson["payload"]["kick"]))
-            m = {
-                    "user":"SYSTEM",
-                    "text":"User %s banned %s from the room" % (reqjson['payload']['user'],reqjson['payload']['kick'])
-                }
+            self.pushSystemMsg("User %s banned %s from the room" % (reqjson['payload']['user'],reqjson['payload']['kick']),room,'syswarn')
             
-            self.cursor.execute("INSERT INTO messages (ts,room,msg) VALUES (?,?,?)",(time.time(),room,json.dumps(m)))
-            self.conn.commit()
-        
         return { "status" : "ok" }
     
-    
-    
+
     
     def processjoinRoom(self,reqjson):
         ''' Process a request from a user to login to a room
@@ -366,17 +379,17 @@
         required = ['roomName','user','userpass']
         for i in required:
             if i not in reqjson['payload']:
-                return 400
+                return self.returnFailure(400)
                 
         
         room = self.getRoomID(reqjson['payload']["roomName"])
         
         if not room:
-            return 400
+            return self.returnFailure(400)
         
         
         if reqjson["payload"]["user"] == "SYSTEM":
-            return 403
+            return self.returnFailure(403)
         
         # Check whether that user is authorised to connect to that room
         self.cursor.execute("SELECT username, room,passhash from users where username=? and room=?",(reqjson['payload']['user'],room))
@@ -389,7 +402,7 @@
         # Now we need to verify they've supplied a correct password for that user
         stored = r[2].encode("utf-8")
         if stored != bcrypt.hashpw(reqjson['payload']['userpass'].encode('utf-8'),stored):
-            return 403
+            return self.returnFailure(403)
         
             
         # Tidy older messages away.
@@ -402,16 +415,8 @@
         
         
         # 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()
-        
+        msgid = self.pushSystemMsg("User %s joined the room" % (reqjson['payload']['user']),room)
+
         # 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()
@@ -423,41 +428,40 @@
                 
         # 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))
+        
+        
+        # Create a session for the user
+        sesskey = "%s-%s" % (reqjson['payload']["roomName"],self.genSessionKey())
+        self.cursor.execute("INSERT INTO sessions (username,sesskey) values (?,?)", (reqjson['payload']['user'],sesskey))
         self.conn.commit()
-        
-        
-        return {"status":"ok","last":last}
+                
+        return {"status":"ok","last":last,"session":sesskey,"syskey":self.syskey}
         
         
     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
+            return self.returnFailure(400)
         
         room = self.getRoomID(reqjson['payload']["roomName"])
         
         if not room:
-            return 400
+            return self.returnFailure(400)
         
         # Check the user is actually in the room and authorised
         if not self.validateUser(reqjson['payload']):
-            return 400
+            return self.returnFailure(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()
         
+        # Delete their session
+        self.cursor.execute("DELETE FROM sessions where username=? and sesskey = ?", (reqjson['payload']['user'],reqjson['payload']["sesskey"]))
+        
         # 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()        
-        
+        self.pushSystemMsg("User %s left the room" % (reqjson['payload']['user']),room)
         return {"status":"ok"}
     
     
@@ -469,19 +473,19 @@
         '''
         
         if not self.validateUser(reqjson['payload']):
-            return 403
+            return self.returnFailure(403)
         
         
         if "roomName" not in reqjson['payload'] or "msg" not in reqjson['payload']:
-            return 400
+            return self.returnFailure(400)
         
         room = self.getRoomID(reqjson['payload']["roomName"])
         print room
         if not room:
-            return 400
+            return self.returnFailure(400)
 
             
-        self.cursor.execute("INSERT INTO messages (ts,room,msg) VALUES (?,?,?)",(time.time(),room,reqjson['payload']['msg']))
+        self.cursor.execute("INSERT INTO messages (ts,room,msg,user) VALUES (?,?,?,?)",(time.time(),room,reqjson['payload']['msg'],reqjson['payload']['user']))
         msgid = self.cursor.lastrowid
         self.conn.commit()
         
@@ -500,30 +504,79 @@
                 "last" : last
             }
         
-        
-    def fetchMsgs(self,reqjson):
-        ''' Check to see if there are any new messages in the room
-        
-        curl -v -X POST http://127.0.0.1:8090/ -H "Content-Type: application/json" --data '{"action":"pollMsg","payload":"{\"roomName\":\"BenTest\", \"mylast\":1,\"user\":\"ben2\"}"}'
-        
+
+
+    def sendDirectMsg(self,reqjson):
+        ''' Push a message to a user in the same room
         '''
         
         if not self.validateUser(reqjson['payload']):
-            return 403
-
-        if "mylast" not in reqjson['payload']:
-            return 400
+            return self.returnFailure(403)
+                
+        required = ['roomName','msg','to']
+        for i in required:
+            if i not in reqjson['payload']:
+                return self.returnFailure(400)
         
         room = self.getRoomID(reqjson['payload']["roomName"])
         print room
         if not room:
-            return 400        
-        
-        self.cursor.execute("""SELECT id,msg,ts FROM messages
+            return self.returnFailure(400)
+
+        # Check the other user is in the room and active
+        self.cursor.execute("SELECT username from users where username=? and room=? and active=1",(reqjson['payload']['to'],room))
+        r = self.cursor.fetchone()
+        
+        if not r:
+            return self.returnFailure(400)
+            
+        self.cursor.execute("INSERT INTO messages (ts,room,msg,user,touser) VALUES (?,?,?,?,?)",(time.time(),room,reqjson['payload']['msg'],reqjson['payload']['user'],reqjson['payload']['to']))
+        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",
+                "msgid" : msgid,
+                "last" : last
+            }
+        
+
+
+        
+    def fetchMsgs(self,reqjson):
+        ''' Check to see if there are any new messages in the room
+        
+        curl -v -X POST http://127.0.0.1:8090/ -H "Content-Type: application/json" --data '{"action":"pollMsg","payload":"{\"roomName\":\"BenTest\", \"mylast\":1,\"user\":\"ben2\"}"}'
+        
+        '''
+
+        if "mylast" not in reqjson['payload']:
+            return self.returnFailure(400)
+        
+        room = self.getRoomID(reqjson['payload']["roomName"])
+        print room
+        if not room:
+            return self.returnFailure(400)
+
+
+        if not self.validateUser(reqjson['payload']):
+            return self.returnFailure(403,reqjson['payload'],room)
+        
+        self.cursor.execute("""SELECT id,msg,ts,user,touser FROM messages
             WHERE room=? AND
+            (touser = 0 OR touser = ?) AND 
             id > ?
             ORDER BY ts ASC           
-            """,(room,reqjson['payload']['mylast']))
+            """,(room,reqjson['payload']['user'],reqjson['payload']['mylast']))
         
         r = self.cursor.fetchall()
         
@@ -548,9 +601,17 @@
             return False
         
         
+        # Validate the session information
+        self.cursor.execute("SELECT username from sessions where username=? and sesskey=?",(payload['user'],payload['sesskey']))
+        r = self.cursor.fetchone();
+        
+        if not r:
+            return False
+        
+        
         room = self.getRoomID(payload["roomName"])
         if not room:
-            return 400        
+            return False        
         
         
         
@@ -578,10 +639,24 @@
         return r[0]
     
 
+
+    def triggerClean(self,reqjson):
+        ''' Trigger a clean of old messages etc
+        '''
+        
+        # Tidy messages older than 10 minutes
+        self.tidyMsgs(time.time() - 600);
+        
+        
+        return {'status':'ok'}
+        
+        
+
     def tidyMsgs(self,thresholdtime,room=False):
         ''' Remove messages older than the threshold time
         '''
         
+        print "Tidying"
         if room:
             # Tidy from a specific room
             self.cursor.execute("DELETE FROM messages where ts < ? and room = ?",(thresholdtime,room))
@@ -592,19 +667,151 @@
             self.conn.commit()
 
 
+        # Tidy away any failure messages
+        self.cursor.execute("DELETE FROM failuremsgs  where expires < ?",(time.time(),))
+
+
+
+    def pushSystemMsg(self,msg,room,verb="sysinfo"):
+        ''' Push a message from the SYSTEM user into the queue
+        '''
+        m = {
+            "text":msg,
+            "verb":verb
+        }
+        
+        m = self.encryptSysMsg(json.dumps(m))
+        
+        self.cursor.execute("INSERT INTO messages (ts,room,msg,user) VALUES (?,?,?,'SYSTEM')",(time.time(),room,m))
+        msgid = self.cursor.lastrowid
+        self.conn.commit()
+        return msgid
+
+
+    def pushFailureMessage(self,user,room,msg):
+        ''' Record a failure message against a user
+        
+        See LOC-14
+        
+        '''
+        self.cursor.execute("INSERT INTO failuremsgs (username,room,expires,msg) values (?,?,?,?)",(user,room,time.time() + 300,msg))
+        self.conn.commit()
+        
+        
+
+
+    def returnFailure(self,status,reqjson=False,room=False):
+        ''' For whatever reason, a request isn't being actioned. We need to return a status code
+        
+        However, in some instances, we may allow a HTTP 200 just once in order to send the user
+        information on why their next request will fail 
+        '''
+        
+        # TODO - implement the failure handling stuff
+        
+        if reqjson and room:
+            # Check whether there's a failure message or not 
+            self.cursor.execute("SELECT msg from failuremsgs where username=? and room=?",(reqjson['user'],room))
+            r = self.cursor.fetchone()
+            
+            if not r:
+                # No message to return
+                return status
+            
+            # Otherwise, return the message and remove it
+            self.cursor.execute("DELETE from failuremsgs where username=? and room=?",(reqjson['user'],room))
+            self.conn.commit()
+            return {"status":"errmessage","text":r[0]}
+        
+        
+        return status
+        
+
+
+    def encryptSysMsg(self,msg):
+        ''' Encrypt a message from system - LOC-13
+        
+            This isn't so much for protection of the data in memory (as the key is also in memory) as it
+            is to protect against a couple of things you could otherwise do in the client. See LOC-13 for
+            more info.
+        
+        '''
+        
+        crypted = self.gpg.encrypt(msg,None,passphrase=self.syskey,symmetric="AES256",armor=False)
+        return crypted.data.encode('base64')
+        
+
+
+    def genSessionKey(self,N=48):
+        return ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits + '/=?&@#%^()+,.<>:!').encode('utf-8') for _ in range(N))
+
+
+
+    def genpassw(self,N=16):
+        ''' Generate a random string of chars to act as an encryption password
+        '''
+        
+        return ''.join(random.SystemRandom().choice(string.ascii_letters + string.digits).encode('utf-8') for _ in range(N))
+
+    
+
+
 
     def test(self):
         return ['foo']
 
 
 
+# These will be handled properly later
+passw = 1234
+bindpoint = "https://127.0.0.1:8090" 
+
 
 # Create a global instance of the wrapper so that state can be retained
-msghandler = MsgHandler()
+#
+# We pass in the password we generated for the scheduler thread to use
+msghandler = MsgHandler(passw)
+
+
+# Create the scheduler function
+def taskScheduler(passw,bindpoint):
+    
+    
+    # Ignore cert errors
+    ctx = ssl.create_default_context()
+    ctx.check_hostname = False
+    ctx.verify_mode = ssl.CERT_NONE
+    
+    data = json.dumps({"action":'schedulerTrigger',
+               "pass": passw
+            })
+    
+    while True:
+        time.sleep(60)
+        
+        try:
+            req = urllib2.Request(bindpoint, data, {'Content-Type': 'application/json'})
+            f = urllib2.urlopen(req,context=ctx)
+            response = f.read()
+            f.close()  
+        except:
+            # Don't let the thread abort just because one request went wrong
+            continue
+
+
+
 
 
 if __name__ == '__main__':
+    thread.start_new_thread(taskScheduler,(passw,bindpoint))
+
     # Bind to PORT if defined, otherwise default to 8090.
     port = int(os.environ.get('PORT', 8090))
     app.run(host='0.0.0.0', port=port,debug=True,ssl_context='adhoc')
 
+
+
+
+
+
+