diff -r 727a907b8305 -r a134031209be Cooperation/CooperationClient.py --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Cooperation/CooperationClient.py Sun Mar 21 19:34:15 2010 +0000 @@ -0,0 +1,258 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2010 Detlev Offenbach <detlev@die-offenbachs.de> +# + +""" +Module implementing the client of the cooperation package. +""" + +import collections + +from PyQt4.QtCore import QObject, pyqtSignal, QProcess, QRegExp +from PyQt4.QtNetwork import QHostInfo, QHostAddress, QAbstractSocket + +from .CooperationServer import CooperationServer +from .Connection import Connection + +class CooperationClient(QObject): + """ + Class implementing the client of the cooperation package. + + @signal newMessage(user, message) emitted after a new message has + arrived (string, string) + @signal newParticipant(nickname) emitted after a new participant joined (string) + @signal participantLeft(nickname) emitted after a participant left (string) + @signal connectionError(message) emitted when a connection error occurs (string) + @signal cannotConnect() emitted, if the initial connection fails + """ + newMessage = pyqtSignal(str, str) + newParticipant = pyqtSignal(str) + participantLeft = pyqtSignal(str) + connectionError = pyqtSignal(str) + cannotConnect = pyqtSignal() + + def __init__(self): + """ + Constructor + """ + QObject.__init__(self) + + self.__server = CooperationServer() + self.__peers = collections.defaultdict(list) + + self.__initialConnection = None + + envVariables = ["USERNAME.*", "USER.*", "USERDOMAIN.*", + "HOSTNAME.*", "DOMAINNAME.*"] + environment = QProcess.systemEnvironment() + found = False + for envVariable in envVariables: + for env in environment: + if QRegExp(envVariable).exactMatch(env): + envList = env.split("=") + if len(envList) == 2: + self.__username = envList[1].strip() + found = True + break + + if found: + break + + if self.__username == "": + self.__username = self.trUtf8("unknown") + + self.__server.newConnection.connect(self.__newConnection) + + def server(self): + """ + Public method to get a reference to the server. + + @return reference to the server object (CooperationServer) + """ + return self.__server + + def sendMessage(self, message): + """ + Public method to send a message. + + @param message message to be sent (string) + """ + if message == "": + return + + for connectionList in self.__peers.values(): + for connection in connectionList: + connection.sendMessage(message) + + def nickName(self): + """ + Public method to get the nick name. + + @return nick name (string) + """ + return "{0}@{1}:{2}".format( + self.__username, + QHostInfo.localHostName(), + self.__server.serverPort() + ) + + def hasConnection(self, senderIp, senderPort = -1): + """ + Public method to check for an existing connection. + + @param senderIp address of the sender (QHostAddress) + @param senderPort port of the sender (integer) + @return flag indicating an existing connection (boolean) + """ + if senderPort == -1: + return senderIp in self.__peers + + if senderIp not in self.__peers: + return False + + for connection in self.__peers[senderIp]: + if connection.peerPort() == senderPort: + return True + + return False + + def hasConnections(self): + """ + Public method to check, if there are any connections established. + + @return flag indicating the presence of connections (boolean) + """ + for connectionList in self.__peers.values(): + if connectionList: + return True + + return False + + def __removeConnection(self, connection): + """ + Private method to remove a connection. + + @param connection reference to the connection to be removed (Connection) + """ + if connection.peerAddress() in self.__peers and \ + connection in self.__peers[connection.peerAddress()]: + self.__peers[connection.peerAddress()].remove(connection) + nick = connection.name() + if nick != "": + self.participantLeft.emit(nick) + + connection.deleteLater() + + def disconnectConnections(self): + """ + Public slot to disconnect from the chat network. + """ + for connectionList in self.__peers.values(): + while connectionList: + self.__removeConnection(connectionList[0]) + + def __newConnection(self, connection): + """ + Private slot to handle a new connection. + + @param connection reference to the new connection (Connection) + """ + connection.setGreetingMessage(self.__username, + self.__server.serverPort()) + + connection.error.connect(self.__connectionError) + connection.disconnected.connect(self.__disconnected) + connection.readyForUse.connect(self.__readyForUse) + + def __connectionError(self, socketError): + """ + Private slot to handle a connection error. + + @param socketError reference to the error object (QAbstractSocket.SocketError) + """ + connection = self.sender() + if socketError != QAbstractSocket.RemoteHostClosedError: + if connection.peerPort() != 0: + msg = "{0}:{1}\n{2}\n".format( + connection.peerAddress().toString(), + connection.peerPort(), + connection.errorString() + ) + else: + msg = "{0}\n".format(connection.errorString()) + self.connectionError.emit(msg) + if connection == self.__initialConnection: + self.cannotConnect.emit() + self.__removeConnection(connection) + + def __disconnected(self): + """ + Private slot to handle the disconnection of a chat client. + """ + connection = self.sender() + self.__removeConnection(connection) + + def __readyForUse(self): + """ + Private slot to handle a connection getting ready for use. + """ + connection = self.sender() + if self.hasConnection(connection.peerAddress(), connection.peerPort()): + return + + connection.newMessage.connect(self.newMessage) + connection.getParticipants.connect(self.__getParticipants) + + self.__peers[connection.peerAddress()].append(connection) + nick = connection.name() + if nick != "": + self.newParticipant.emit(nick) + + if connection == self.__initialConnection: + connection.sendGetParticipants() + self.__initialConnection = None + + def connectToHost(self, host, port): + """ + Public method to connect to a host. + + @param host host to connect to (string) + @param port port to connect to (integer) + """ + self.__initialConnection = Connection(self) + self.__newConnection(self.__initialConnection) + self.__initialConnection.participants.connect(self.__processParticipants) + self.__initialConnection.connectToHost(host, port) + + def __getParticipants(self): + """ + Private slot to handle the request for a list of participants. + """ + reqConnection = self.sender() + participants = [] + for connectionList in self.__peers.values(): + for connection in connectionList: + if connection != reqConnection: + participants.append("{0}:{1}".format( + connection.peerAddress().toString(), connection.serverPort())) + reqConnection.sendParticipants(participants) + + def __processParticipants(self, participants): + """ + Private slot to handle the receipt of a list of participants. + + @param participants list of participants (list of strings of "host:port") + """ + for participant in participants: + host, port = participant.split(":") + port = int(port) + + if port == 0: + msg = self.trUtf8("Illegal address: {0}:{1}\n").format(host, port) + self.connectionError.emit(msg) + else: + if not self.hasConnection(QHostAddress(host), port): + connection = Connection(self) + self.__newConnection(connection) + connection.connectToHost(host, port)