def _initPeriodicPingThread(self): self._periodicPingThread = PeriodicPingThread(self.periodicPingInterval, self) self._periodicPingThread.start()
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