示例#1
0
文件: Chat.py 项目: slnc/cs425cp
 def _initPeriodicPingThread(self):
     self._periodicPingThread = PeriodicPingThread(self.periodicPingInterval, self)
     self._periodicPingThread.start()
示例#2
0
文件: Chat.py 项目: slnc/cs425cp
class Chat(object):
    '''Represents a Chat instance.
    
    This is the highest level class in the project. We follow PEP-8 coding conventions
    and therefore all methods that don't start with an underscore are considered public.
    '''
    
    DEFAULT_LISTEN_IP = '127.0.0.1'
    DEFAULT_LISTEN_PORT = 9090
    DEFAULT_UID = 90
    OFFLINE = 0
    ONLINE = 1
    KEY = 's3cr3tsaucefryou'
    DEFAULT_PERIODIC_PING_INTERVAL = 60.0 # seconds
    
    def __init__(self, _address=(DEFAULT_LISTEN_IP, DEFAULT_LISTEN_PORT), _uid=DEFAULT_UID, enable_logging=True, _periodicPingInterval=DEFAULT_PERIODIC_PING_INTERVAL):
        # Stress testing is very hard on resources. We disable logging when running stress tests
        if enable_logging:
            # Logging
            self._logfile = "log/log.%s.%s" % (_address[0], _address[1])
            self._logger = logging.getLogger("%s.%s" % (_address[0], _address[1]))
            hdlr = logging.FileHandler(self._logfile)
            formatter = logging.Formatter('%(asctime)s %(message)s')
            hdlr.setFormatter(formatter)
            self._logger.addHandler(hdlr)
            self._logger.setLevel(logging.DEBUG)
        else:
            self._logger = None
        
        self.address = _address
        self.uid = _uid
        self.storedPackets = []
        self.sentPackets = []
        self.setDownloadDir('downloads')
        self.periodicPingInterval = _periodicPingInterval
        self.fileOffersReceived = {}
        self.fileOffersSent = {}
        self.startTime = None
        self._notifyData = None
        self._initGroupDependentState()
        
        self._startComplete = False
    
    
    def _initGroupDependentState(self):
        self.nick = None
        self.channels = {'lobby': Channel()}
        self.status = Chat.OFFLINE
        self.messages = []
        self.groupView = GroupView()
        self.orderer = Orderer(self, self.address)
                

    def enableConsoleLogging(self):
        '''Starts outputting all log messages to console when called.'''
        ch = logging.StreamHandler()
        ch.setLevel(logging.DEBUG)
        formatter = logging.Formatter('%(asctime)s %(message)s', '%H:%m:%S')
        ch.setFormatter(formatter)
        self._logger.addHandler(ch)

    
    def silentStart(self):
        '''Opens a listen port but doesn't send any message at all (useful for debugging)'''
        self.groupView.readGroupViewFromFile(self.uid)
 
        try:
            self.listener = Listener(self.address[0], self.address[1], self)
            self.listener.start()
            logMsg = 'Client started (%s)' % str(self.getListenAddress())
            self._log(logMsg)
            data = {'channel' : 'network', 'msg' : logMsg}
            pub.sendMessage('log', data)
        except:
            raise Exception("Failed to bind, socket already in use!")
        

    def start(self):
        '''Starts a chat instance, opens a socket and, if the client belongs to a group 
        it will try to connect to each member in the last GroupView it has.
        '''
        self._startComplete = False
        self.silentStart()
        self.startTime = time.time()
        if self.groupView.isInGroup():
            self.groupView.setUserStatus(self.address, Chat.ONLINE)
            pub.sendMessage('update', {'field' : 'group'})
            for addr in self.groupView.getGroup():
                if addr == self.address:
                    continue
                
                try:
                    failures = self._sendPacket(HiWrapper(), addr)
                    if failures:
                        raise Exception
                except Exception:
                    pass # user is seemingly offline, go to the next
                else:
                    break # packet sent, they now know that we're back online

        
        self._initPeriodicPingThread()
        self._startComplete = True
        
    
    def isStartComplete(self):
        '''Method returns true if the starting process has finished. This is necessary when stress
        testing the application because of timeouts and possible CPU congestion.'''
        while not self._startComplete:
            time.sleep(0.1)
        return self._startComplete
    

    def stop(self):
        '''Closes the listening socket without notifying anyone. self will know that 
        it has stopped but nobody else in the group will notice until one of them tries
        to connect to him. 
        
        This can simulate one kind of network failure.'''
        #self.endTime = time.time()
        if self.listener and self.listener.is_alive():
            self.listener.shutdown()
            self.listener.join()
            self._periodicPingThread.shutdown()
            self._startComplete = False
            logMsg = 'Client stopped (%s)' % str(self.getListenAddress())
            self._log(logMsg)
            data = {'channel' : 'network', 'msg' : logMsg}
            pub.sendMessage('log', data)
            
        else:
            raise Exception("You are trying to stop a non living thread! (%s)" % sys.exc_info()[0])
    

    def setPeriodicPingSleep(self, _interval):
        '''Changes the interval in seconds between pinging everybody in the GroupView to see 
        if they are alive or not.'''
        
        # The next lines are necessary because of the havoc caused by the stress testing script
        while not self._startComplete:
            time.sleep(0.1)
            
        old = self.periodicPingInterval
        self.periodicPingInterval = _interval
        self._periodicPingThread.changeInterval(self.periodicPingInterval)
        self._log('Periodic ping changed from %ds to %ds' % (old, _interval))
    
        
        
    def sendOrderingSuggestion(self, mid, mySeq, sender):
        net.send(OrderingSuggestion(mid, mySeq), self.getListenAddress(), sender)

    def sendFinalOrdering(self, mid, mySeq):
        for addr in self.groupView.returnOnlineUsers():
            net.send(FinalOrdering(mid, mySeq), self.getListenAddress(), addr)
    
        
    ''' ---------------------- File handling  --------------------------'''
    def getDownloadDir(self):
        return self.downloadDir
    
    def setDownloadDir(self, newdir):
        self.downloadDir = newdir
        if not os.path.exists(newdir):
            os.makedirs(newdir)
        self._log("DownloadDir changed to '%s'" % newdir)
    
    
    def offerFile(self, filename, target):
        if not os.path.exists(filename):
            raise FileNotFoundException("File %s not found" % filename)
        
        if target == self.getListenAddress():
            raise Exception("Cannot offer a file to myself")
        
        if target not in self.groupView.getGroup():
            raise Exception("Target %s is not in the group" % str(target))
        
        somecookie = hashlib.sha1("%s%s%s" % (str(random.random()), filename, str(target))).hexdigest()
        foff = FileOffer(somecookie, filename, time.localtime(time.time() + 10*60), self.getListenAddress(), target)
        self._sendPacket(foff, target)
        self.fileOffersSent[somecookie] = {'filename': filename, 'expires_on': foff.expires_on, 'sender': self, 'recipient': target }
        self._log("File '%s' offered to %s" % (filename, str(self.getNickOfAddress(target))))
        return somecookie
    
    
    def getFileOffers(self):
        return self.fileOffersReceived
    
    
    def acceptFileOffer(self, cookie):
        if cookie not in self.fileOffersReceived:
            raise InvalidFileCookieException("File offer %s is not present" % cookie)
        else:
            fileOffer = self.fileOffersReceived[cookie]
            if time.time() > fileOffer['expires_on']:
                raise InvalidFileCookieException("File offer for '%s' has expired, please ask for that file again" % fileOffer['filename'])
            else:
                self._sendPacket(FileOfferAccept(cookie), fileOffer['offerer'])
                self._log("File offer '%s' acceptation sent to %s" % (fileOffer['filename'], self.getNickOfAddress(fileOffer['offerer'])))
    
    
    
    ''' ---------------------- Nick handling  --------------------------'''
    def hasNick(self):
        # TODO acceses to self.nick should be controlled by a mutex because technically
        # a NickChange can arrive and be delivered while the main Chat thread 
        # is here
        return self.nick != None
    
    
    def changeNick(self, newNick):
        '''Changes the current user's nick'''
        if newNick == self.nick:
            return
        
        self._broadcastPacket(NickChange(newNick, self.orderer.getNewMID()))
        
        
    def getNickOfAddress(self, address):
        return self.groupView.getGroup()[address]['nick']
    
        
    def getAddressOfNick(self, _nick):
        g = self.groupView.getGroup()
        for uid in g:
            val = g[uid]
            if val['nick'] == _nick:
                return uid
        
        raise NickNotFoundException("Nick %s is not in the group" % _nick)

        
    def getNick(self):
        if not self.nick:
            raise Exception("No nick!")
        
        return self.nick
        
    

    ''' ---------------------- Basic group and membership handling  --------------------------'''
    def logout(self):
        '''Notifies everybody who is online in the group that self is temporarily disconnecting
        from the chat.'''
        
        self._broadcastPacket(Logout(self.orderer.getNewMID()), False)
        time.sleep(1.0)
        self.stop()
        logMsg = 'Logged out'
        self._log(logMsg)
        data = {'channel' : 'lobby', 'msg' : logMsg}
        pub.sendMessage('log', data)
        
    
    def leaveGroup(self):
        '''Notifies everybody about self's intention to permanently leave the group.'''
        if not self.groupView.isInGroup():
            raise Exception("I'm not in a group!")
        
        self._broadcastPacket(Leave(self.orderer.getNewMID()), False)
        time.sleep(1.0)
        self.stop()
        
    
    def getListenAddress(self):
        return self.address


    def userStatus(self, target_add):
        '''Returns Chat.ONLINE or Chat.OFFLINE depending on what self thinks is his network
        status'''
        return self.groupView.getUserStatus(target_add)


    def createGroup(self):
        '''Creates a chat group. 
        
        At the moment only one chat can be created.'''
        if not self._startComplete:
            raise Exception('Chat is not started!')


        if not self.groupView.isInGroup():
            _initialNick = self.groupView.addUserToGroup(self.address, Chat.ONLINE)
            self.groupView.changeGroupStatus()
            self.nick = _initialNick
            uData = {'field' : 'myNick', 'value' : self.nick}
            pub.sendMessage('update', uData)
            self.channels['lobby'].addUser(self.nick, self.address)
        else:
            return Exception("Cannot create group, user %i already in a group" % self.uid)


    def addUser(self, target_add):
        '''Adds a user to the group allowing him to send and receive commands to and from the group.
        
        Any user who is already in a group can add new users.'''
        
        if not self.groupView.isInGroup():
            raise Exception("Cannot add user to group, b/c i'm not in a group")
        else:
            if self.groupView.userIsInGroup(target_add):
                raise Exception("Impossible, %s is already in the group" % str(target_add))
            
            self._broadcastPacket(NewUser(target_add, self.getListenAddress(), self.orderer.getNewMID()))
    
    
    def getOnlineUsers(self):
        return self.groupView.returnOnlineUsers()
    
        
    def getOfflineUsers(self):
        return self.groupView.returnOfflineUsers()


    ''' ---------------------- Channel functionality  --------------------------'''
    def getUserInfo(self, userNick):
        info = {'channels': []}
        
        for c in self.channels.values():
            if userNick in c.getUsers():
                info['channels'].append(c.getName()) 
        
        return info


    def isInChannel(self, channelName):
        return self.nick in self.getChannel(channelName).getUsers()


    def getChannel(self, channelName = 'lobby'):
        if channelName not in self.channels:
            raise Exception('Doesn\'t have channel: %s' % channelName)
        else:
            return self.channels[channelName]


    def getChannels(self):
        '''Returns channels that a user is in not including "lobby"'''
        channels = {}
        for chan in self.channels:
            if chan != 'lobby' and self.address in self.channels[chan].getUserAddresses():
                channels[chan] = self.channels[chan]
                
        return channels

    def joinChannel(self, channelName):
        self._broadcastPacket(ChannelChange(self.orderer.getNewMID(), self.nick, self.address, channelName))


    def leaveChannel(self, channelName):
        if channelName not in self.channels:
            raise Exception('Trying to leave a nonexisting channel')
        else:
            self._broadcastPacket(ChannelChange(self.orderer.getNewMID(), self.nick, self.address, channelName, False))

    ''' ---------------------- ChannelLogHistory  --------------------------'''
    def requestChannelLogHistory(self, channelName):
        for userAddr in self.getChannel(channelName).getUserAddresses():
            if userAddr == self.address:
                continue
            if userAddr in self.getOnlineUsers():
                self._log("Sending request to %s" % str(userAddr))
                failure = self._sendPacket(ChannelLogRequest(self.address, channelName, self.startTime), userAddr)
                time.sleep(1) #there appears to be a race condition when running under single machine

    ''' ---------------------- Packet handling  --------------------------'''
    def notifyAfterNextDeliveryOfType(self, thread, packet_type_name):
        '''Call t's .start() after the next delivery of a command not related to ordering
        
        (Used to measure time until delivery in performance.py)'''
        self._notifyData = (thread, packet_type_name)


    def receivedPackets(self):
        '''Returns all the received commands'''
        return self.storedPackets


    ''' ---------------------- Message handling  --------------------------'''
    def sendMessage(self, msg, channel = 'lobby'):
        '''Sends a message to the chat channel.'''
        self._broadcastPacket(Message(msg, self.nick, channel, self.orderer.getNewMID()))


    def getMessages(self):
        return self.messages

    ''' ---------------------- Encryption  --------------------------'''
    def turnOnEncryption(self):
        chat.turnOnEncryption()

    def turnOffEncryption(self):
        chat.turnOffEncryption()

    def encryptionStatus(self):
        print chat.ENCRYPT

    ''' ---------------------- Private methods  --------------------------''' 
    def _log(self, msg):
        if self._logger:
            self._logger.debug(msg)
    
    
    
    def _sendPacket(self, msg, target_address, handle_failures=True):
        '''Sends a command to a single target via unicast.
        
        If there is an error it will return target_address. It will return
        None otherwise.'''
        self.sentPackets.append((msg, target_address))
        return net.send(msg, self.address, target_address)
    
    
    
    def _broadcastPacket(self, packet, handleFailures=True):
        '''Sends the given packet to every online user'''
        self.sentPackets.append((packet, ('0.0.0.0', 0)))

        failures = net.broadcast(packet, self.address, self.groupView.returnOnlineUsers(), self.orderer)
        
        if handleFailures and len(failures) > 0:
            self._handleNetworkFailures(failures)
            
            
    def _handleNetworkFailures(self, failures):
        if len(failures) == 0:
            return
            
        for failed_client in failures:
            if failed_client == None:
                continue
            
            if self.groupView.getUserStatus(failed_client) == Chat.ONLINE:
                self.groupView.setUserStatus(failed_client, Chat.OFFLINE)
                logMsg = 'Member %s disconnected' % str(failed_client)
                self._log(logMsg)
                data = {'channel' : 'lobby', 'msg' : logMsg}
                pub.sendMessage('log', data)
                pub.sendMessage('update', {'field' : 'group'})
                finalSeq = self.orderer._updateRecipients(self.getOnlineUsers())
                if finalSeq:
                    self.sendFinalOrdering(finalSeq['mid'], finalSeq['seq'])
    
        self._broadcastMyGroupView(False)


    def _initPeriodicPingThread(self):
        self._periodicPingThread = PeriodicPingThread(self.periodicPingInterval, self)
        self._periodicPingThread.start()
    
    
    def _timeForPeriodicPing(self):
        # print '----------------------------------------------------------'
        '''Called by PeriodicPingThread when it's time to ping people'''
        changed = False
        
        for addr in self.groupView.getGroup():
            try:
                failures = self._sendPacket(HiWrapper(), addr)
                if failures:
                    raise Exception
            except Exception: # user offline
                try:
                    if self.groupView.getUserStatus(addr) != Chat.OFFLINE:
                        logMsg = 'Member %s disconnected' % str(addr)
                        self._log(logMsg)
                        data = {'channel' : 'lobby', 'msg' : logMsg}
                        pub.sendMessage('log', data)
                        self.groupView.setUserStatus(addr, Chat.OFFLINE)
                        changed = True
                except Exception:
                    logMsg = 'Member %s disappeared during periodic ping' % str(addr)
                    self._log(logMsg)
                    data = {'channel' : 'network', 'msg' : logMsg}
                    pub.sendMessage('log', data)
                    changed = True
            else: # user online
                try:
                    if self.groupView.getUserStatus(addr) != Chat.ONLINE:
                        igv = InitialGroupView(self.groupView.getGroup()) 
                        self._sendPacket(igv, addr)
                        logMsg = 'Member %s is online' % self.geNick()
                        self._log(logMsg)
                        data = {'channel' : 'lobby', 'msg' : logMsg}
                        pub.sendMessage('log', data)
                        self.groupView.setUserStatus(addr, Chat.ONLINE)
                        changed = True
                except Exception:
                    logMsg = 'Member %s disappeared during periodic ping' % str(addr)
                    self._log(logMsg)
                    data = {'channel' : 'network', 'msg' : logMsg}
                    pub.sendMessage('log', data)
                    
        if changed:
            pub.sendMessage('update', {'field' : 'group'})
            finalSeq = self.orderer._updateRecipients(self.getOnlineUsers())
            if finalSeq:
                self.sendFinalOrdering(finalSeq['mid'], finalSeq['seq'])
            self._broadcastMyGroupView()


        
    def _broadcastMyGroupView(self, handleNetworkFailures=True):
        self._log('GroupUpdate sent')
        self._broadcastPacket(GroupUpdate(self.groupView.getGroup(), self.orderer.getNewMID()), handleNetworkFailures)
        
    
    def _newMessageReceived(self, message):
        self.messages.append(message)
        self.channels[message.channel].messages.append(message)
        

    def _pingNewUser(self, address):
        igv = InitialGroupView(self.groupView.getGroup())
        icv = InitialChannelView(self.channels)
        out = self._sendPacket(igv, address)
        self._sendPacket(icv, address)
        # By default we assume new users are OFFLINE therefore if the new user is ONLINE (= no errors) we take action
        if not out:
            self.groupView.setUserStatus(address, Chat.ONLINE)
            self._broadcastPacket(GroupUpdate(self.groupView.getGroup(), self.orderer.getNewMID()))
            pub.sendMessage('update', {'field' : 'group'})
        
            
    ''' ---------------------- File handling (unicast) --------------------------'''
    def _cleanupOldFileCookies(self):
        limit_time = time.localtime()
        for cookie in self.fileOffersSent:
            if self.fileOffersSent[cookie]['expires_on'] < limit_time:
                del self.fileOffersSent[cookie]
                
        for cookie in self.fileOffersReceived:
            if self.fileOffersReceived[cookie]['expires_on'] < limit_time:
                del self.fileOffersReceived[cookie]
        
    
    def _sendFile(self, cookie):
        self._cleanupOldFileCookies()
        if cookie not in self.fileOffersSent:
            self._log('Nonexistant file cookie \'%s\' received' % cookie)
        else:
            fos = self.fileOffersSent[cookie]
            self._log('File offer \'%s\' acceptation received from %s' % (fos['filename'], str(self.getNickOfAddress(fos['recipient']))))
            # use with statement, so file will automatically close
            with open(fos['filename'], 'rb') as f:
                self._sendPacket(FileData(cookie, base64.b64encode(f.read())), fos['recipient'])
                
            self._log('File \'%s\' sent to %s' % (fos['filename'], self.getNickOfAddress(fos['recipient'])))
    
    
    def _receiveFile(self, cookie, data):
        self._cleanupOldFileCookies()
        if cookie not in self.fileOffersReceived:
            self._log('File data received for unknown cookie, ignoring data.') 
        else:
            fos = self.fileOffersReceived[cookie]
            
            # Make sure we are not overwriting an existing file at downloadDir
            fname = "%s/%s" % (self.getDownloadDir(), os.path.basename(fos['filename']))
            i = 0
            while os.path.exists(fname):
                fname = "%s/%s.%d" % (self.getDownloadDir(), os.path.basename(fos['filename']), i)    
            
            with open(fname, 'wb') as fd:
                fd.write(base64.b64decode(data))
                os.fsync(fd)

            self._log('File \'%s\' received from %s' % (fos['filename'], str(self.getNickOfAddress(fos['offerer']))))
            
    
    def _recvBroadcastedPacket(self, packet, sender):
        seqNumber = self.orderer.recvBroadcastedPacket(packet, sender)
        self.sendOrderingSuggestion(packet.id, seqNumber, sender)
    
    def _handleChannelLogRequest(self, sender, channelName, _startTime):
        if self.startTime < _startTime:
            for packet in self.storedPackets:
                if packet.__class__.__name__ == 'Message':
                    if packet.channel == channelName and packet.mtime < _startTime:
                        self._log("sending back packet %s" % packet.text)
                        self._sendPacket(ChannelLogReturn(self.address, packet, packet.channel, packet.mtime, packet.nick, packet.id), sender)
   
    def _handleChannelLogReturn(self, packet):
        self._log("handling return")
        self.channels[packet.channel].insertMessage(packet.text)

    ''' ---------------------- Command reception  --------------------------'''
    def _deliverPacket(self, packet, sender):
        '''_delivery_ of the different packets.
        
        The delivery code assumes that the packets that need to be ordered are already ordered'''
        viewChanged = False
        
        packet.delivery_time = time.time()
            
        self.storedPackets.append(packet)
        
        if chat.DEBUG:
            print "\nxxxxxxxxxxxx %s _deliverPacket(%s) xxxxxxxxxxxxx" % (self.getListenAddress(), packet.__class__.__name__)
            
        if self.groupView.isInGroup() and not self.groupView.userIsInGroup(sender):
            logMsg = 'Packet received from unauthorized agent %s' % str(sender)
            self._log(logMsg)
            data = {'channel' : 'network', 'msg' : logMsg, 'time' : packet.delivery_time}
            pub.sendMessage('log', data)
            return
        

        if packet.__class__.__name__ == 'OrderingSuggestion':

            finalSeq = self.orderer.recvOrderingSuggestion(packet, sender)
            if finalSeq:
                self.sendFinalOrdering(packet.mid, finalSeq)

        elif packet.__class__.__name__ == 'FinalOrdering':
            self.orderer.recvFinalOrdering(packet, sender)
        
        elif packet.__class__.__name__ == 'InitialGroupView':
            logMsg = 'InitialGroupView received from %s' % str(sender)
            self._log(logMsg)
            data = {'channel' : 'network', 'msg' : logMsg, 'time' : packet.delivery_time}
            pub.sendMessage('log', data)
            
            if not self.groupView.isInGroup():
                logMsg = 'Joined Group through %s' % str(sender)
                self._log(logMsg)
                data2 = {'channel' : 'lobby', 'msg' : logMsg, 'time' : packet.delivery_time}
                pub.sendMessage('log', data2)
                self.groupView.inGroup = True
        
            new_groupview = {}
            new_lobbyChannel = Channel('lobby')
            for addr in packet.group:
                # update our own nick 
                if addr == self.getListenAddress():
                    self.nick = packet.group[addr]['nick']
                    uData = {'field' : 'myNick', 'value' : self.nick}
                    pub.sendMessage('update', uData)
                
                new_groupview[addr] = packet.group[addr]
                new_lobbyChannel.addUser(packet.group[addr]['nick'], addr)
                
            self.channels['lobby'] = new_lobbyChannel
            self.groupView.setGroup(new_groupview)

            uData = {'field' : 'group'}
            pub.sendMessage('update', uData)
            
        elif packet.__class__.__name__ == 'InitialChannelView':
            self.channels = packet.channels
    
        elif packet.__class__.__name__ == 'GroupUpdate':
            logMsg = 'GroupUpdate received from %s' % self.getNickOfAddress(sender)
            self._log(logMsg)
            data = {'channel' : 'network', 'msg' : logMsg, 'time' : packet.delivery_time}
            pub.sendMessage('log', data)
            # If we receive a GroupView and we're not in a Group yet it means we have just been added
            if not self.groupView.isInGroup():
                logMsg = 'Received GroupUpdate from %s but I am not in the group. Hell is frozen.' % str(sender)
                self._log(logMsg)
                data2 = {'channel' : 'network', 'msg' : logMsg, 'time' : packet.delivery_time}
                pub.sendMessage('log', data2)
                return
            
            for addr in packet.group:
                changed = self._checkAddressForGroupViewChanges(addr, packet.group[addr]['status'])
                if changed and not viewChanged:
                    viewChanged = True

        
        elif packet.__class__.__name__ == 'Message':
            logMsg = 'Message delivered from %s (#%s): %s' % (self.getNickOfAddress(sender), packet.channel, packet.text)
            self._log(logMsg)
            data = {'channel' : 'network', 'msg' : logMsg, 'time' : packet.delivery_time}
            pub.sendMessage('log', data)
            if packet.channel == 'lobby' or self.nick in self.getChannel(packet.channel).users:
                self._log('Message displayed (#%s) %s says: %s' % (packet.channel, self.getNickOfAddress(sender), packet.text))
                msg = '%s says: %s' % (self.getNickOfAddress(sender), packet.text)
                mData = {'channel' : packet.channel, 'msg' : msg, 'time' : packet.delivery_time}
                pub.sendMessage('message', mData)
            self._newMessageReceived(packet)
            
            
        elif packet.__class__.__name__ == 'Logout':
            self.groupView.setUserStatus(sender, Chat.OFFLINE)
            viewChanged = True
            
            if sender != self.address:
                logMsg = 'Member %s logged out' % self.getNickOfAddress(sender)
                self._log(logMsg)
                data = {'channel' : 'lobby', 'msg' : logMsg, 'time' : packet.delivery_time}
                pub.sendMessage('log', data)


        elif packet.__class__.__name__ == 'Leave':
            self.groupView.removeUser(sender)
            viewChanged = True
            if sender == self.address:
                logMsg = 'Left the group'
                self._log(logMsg)
                data = {'channel' : 'lobby', 'msg' : logMsg, 'time' : packet.delivery_time}
                pub.sendMessage('log', data)
                pub.sendMessage('group.leave')
                self._initGroupDependentState()
            else:
                logMsg = 'Member %s left the group' % str(sender)
                self._log(logMsg)
                lData = {'channel' : 'lobby', 'msg' : logMsg, 'time' : packet.delivery_time}
                pub.sendMessage('log', lData)

        
        elif packet.__class__.__name__ == 'NewUser':
            if self.groupView.userIsInGroup(packet.address):
                raise Exception("User %s is already in the group" % str(packet.address))
            else:
                logMsg = 'New member added %s' % str(packet.address)
                self._log(logMsg)
                data = {'channel' : 'lobby', 'msg' : logMsg, 'time' : packet.delivery_time}
                pub.sendMessage('log', data)
                target_nick = self.groupView.addUserToGroup(packet.address, Chat.OFFLINE)
                self.channels['lobby'].addUser(target_nick, packet.address)
                uData = {'field' : 'group'}
                pub.sendMessage('update', uData)

                if self.getListenAddress() == packet.addedBy:
                    self._pingNewUser(packet.address)
            
            
        elif packet.__class__.__name__ == 'NickChange':
            try:
                prev_nick = self.getNickOfAddress(sender)
    
                free = True
                g = self.groupView.getGroup()
                for uid in g:
                    val = g[uid]
                    if val['nick'] == packet.newNick:
                        free = False
                        break
                
                if free:
                    oldNick = self.getNickOfAddress(sender)
                    for c_name in self.channels:
                        chan = self.channels[c_name]
                        if oldNick in chan.users:
                            chan.removeUser(oldNick)
                            chan.addUser(packet.newNick, sender)
                    self.groupView.updateUserNick(sender, packet.newNick)
                    if sender == self.address:
                        self.nick = packet.newNick
                        self._log("Nick changed to '%s'" % self.nick)
                        data = {'field' : 'myNick', 'value' : self.nick}
                        pub.sendMessage('update', data)
                    else:
                        logMsg = '\'%s\' is now known as \'%s\'' % (prev_nick, packet.newNick)
                        self._log(logMsg)
                        data = {'channel' : 'lobby', 'msg' : logMsg, 'time' : packet.delivery_time}
                        pub.sendMessage('log', data)
                    uData = {'field' : 'nick', 'value' : sender}
                    pub.sendMessage('update', uData)
                elif sender == self.address:
                    logMsg = "Attempt to change nick to '%s' failed" % packet.newNick
                    self._log(logMsg)
                    data = {'channel' : 'lobby', 'msg' : logMsg, 'time' : packet.delivery_time}
                    pub.sendMessage('log', data)
            except DuplicatedNickException:
                print "Error: nick '%s' is already taken" % packet.newNick
            
        elif packet.__class__.__name__ == 'ChannelChange':
            if packet.joinChannel:
                verb = 'joined'
            else:
                verb = 'left'
            logMsg = "%s has %s channel '%s'" % (packet.userName, verb, packet.channelName)
            self._log(logMsg)
            data = {'channel' : 'network', 'msg' : logMsg, 'time' : packet.delivery_time}
            pub.sendMessage('log', data)
            if packet.channelName not in self.channels:
                self.channels[packet.channelName] = Channel(packet.channelName)
                
            if packet.joinChannel:
                self.channels[packet.channelName].addUser(packet.userName, packet.userAddr)
            else:
                self.channels[packet.channelName].removeUser(packet.userName)
            
            if packet.channelName in self.getChannels():
                uData = {'field' : 'channel', 'value' : packet.channelName}
                pub.sendMessage('update', uData)
            


        elif packet.__class__.__name__ == 'FileOffer':
            self.fileOffersReceived[packet.cookie] = {'filename': packet.filename, 'expires_on': packet.expires_on, 'offerer': sender, 'recipient': self}
            logMsg = 'File \'%s\' offered from %s' % (packet.filename, self.getNickOfAddress(sender))
            self._log(logMsg)
            data = {'channel' : 'network', 'msg' : logMsg, 'time' : packet.delivery_time}
            pub.sendMessage('log', data)
            uData = {'field' : 'fileOffers'}
            pub.sendMessage('update', uData)

        elif packet.__class__.__name__ == 'FileOfferAccept':
            self._sendFile(packet.cookie)


        elif packet.__class__.__name__ == 'FileData':
            self._receiveFile(packet.cookie, packet.data)
            uData = {'field' : 'fileOffers'}
            pub.sendMessage('update', uData)
        
        elif packet.__class__.__name__ == 'ChannelLogRequest':
            logMsg = "Received ChannelLogRequest from %s" % self.getNickOfAddress(packet.sender)
            self._log(logMsg)
            data = {'channel' : 'network', 'msg' : logMsg, 'time' : packet.delivery_time}
            pub.sendMessage('log', data)
            self._handleChannelLogRequest(packet.sender, packet.channel, packet.startTime)
        
        elif packet.__class__.__name__ == 'ChannelLogReturn':
            logMsg = "Received ChannelLogReturn from %s" % self.getNickOfAddress(packet.sender)
            self._log(logMsg)
            data = {'channel' : 'network', 'msg' : logMsg, 'time' : packet.delivery_time}
            pub.sendMessage('log', data)
            self._handleChannelLogReturn(packet)

        elif packet.__class__.__name__ == 'HiWrapper':
            self._checkAddressForGroupViewChanges(sender, Chat.ONLINE)
            pass # we can safely discard this packets, they're used to check if a host is online
        
        else:
            raise Exception("Unknown class %s" % packet.__class__.__name__)
        
        if viewChanged:
            pub.sendMessage('update', {'field' : 'group'})
            finalSeq = self.orderer._updateRecipients(self.getOnlineUsers())
            if finalSeq:
                self.sendFinalOrdering(finalSeq['mid'], finalSeq['seq'])

         
        if self._notifyData and packet.__class__.__name__ == self._notifyData[1]:
            self._notifyData[0].start()
            self._notifyData = None

    def _checkAddressForGroupViewChanges(self, addr, currentStatus):
        viewChanged = False
        
        if self.groupView.userIsInGroup(addr) and self.groupView.getUserStatus(addr) == Chat.ONLINE and currentStatus == Chat.OFFLINE:
            logMsg = 'Member %s disconnected' % self.getNickOfAddress(addr)
            self._log(logMsg)
            data = {'channel' : 'lobby', 'msg' : logMsg}
            pub.sendMessage('log', data)
            self.groupView.setUserStatus(addr, Chat.OFFLINE)
            viewChanged = True
            
        elif self.groupView.userIsInGroup(addr) and self.groupView.getUserStatus(addr) == Chat.OFFLINE and currentStatus == Chat.ONLINE:
            logMsg = 'Member %s is online' % self.getNickOfAddress(addr)
            self._log(logMsg)
            data = {'channel' : 'lobby', 'msg' : logMsg}
            pub.sendMessage('log', data)
            self.groupView.setUserStatus(addr, Chat.ONLINE)
            viewChanged = True
            
        if viewChanged:
            pub.sendMessage('update', {'field' : 'group'})
            
        return viewChanged