Cooperation/CooperationClient.py

changeset 149
a134031209be
child 155
375e3c884874
equal deleted inserted replaced
148:727a907b8305 149:a134031209be
1 # -*- coding: utf-8 -*-
2
3 # Copyright (c) 2010 Detlev Offenbach <detlev@die-offenbachs.de>
4 #
5
6 """
7 Module implementing the client of the cooperation package.
8 """
9
10 import collections
11
12 from PyQt4.QtCore import QObject, pyqtSignal, QProcess, QRegExp
13 from PyQt4.QtNetwork import QHostInfo, QHostAddress, QAbstractSocket
14
15 from .CooperationServer import CooperationServer
16 from .Connection import Connection
17
18 class CooperationClient(QObject):
19 """
20 Class implementing the client of the cooperation package.
21
22 @signal newMessage(user, message) emitted after a new message has
23 arrived (string, string)
24 @signal newParticipant(nickname) emitted after a new participant joined (string)
25 @signal participantLeft(nickname) emitted after a participant left (string)
26 @signal connectionError(message) emitted when a connection error occurs (string)
27 @signal cannotConnect() emitted, if the initial connection fails
28 """
29 newMessage = pyqtSignal(str, str)
30 newParticipant = pyqtSignal(str)
31 participantLeft = pyqtSignal(str)
32 connectionError = pyqtSignal(str)
33 cannotConnect = pyqtSignal()
34
35 def __init__(self):
36 """
37 Constructor
38 """
39 QObject.__init__(self)
40
41 self.__server = CooperationServer()
42 self.__peers = collections.defaultdict(list)
43
44 self.__initialConnection = None
45
46 envVariables = ["USERNAME.*", "USER.*", "USERDOMAIN.*",
47 "HOSTNAME.*", "DOMAINNAME.*"]
48 environment = QProcess.systemEnvironment()
49 found = False
50 for envVariable in envVariables:
51 for env in environment:
52 if QRegExp(envVariable).exactMatch(env):
53 envList = env.split("=")
54 if len(envList) == 2:
55 self.__username = envList[1].strip()
56 found = True
57 break
58
59 if found:
60 break
61
62 if self.__username == "":
63 self.__username = self.trUtf8("unknown")
64
65 self.__server.newConnection.connect(self.__newConnection)
66
67 def server(self):
68 """
69 Public method to get a reference to the server.
70
71 @return reference to the server object (CooperationServer)
72 """
73 return self.__server
74
75 def sendMessage(self, message):
76 """
77 Public method to send a message.
78
79 @param message message to be sent (string)
80 """
81 if message == "":
82 return
83
84 for connectionList in self.__peers.values():
85 for connection in connectionList:
86 connection.sendMessage(message)
87
88 def nickName(self):
89 """
90 Public method to get the nick name.
91
92 @return nick name (string)
93 """
94 return "{0}@{1}:{2}".format(
95 self.__username,
96 QHostInfo.localHostName(),
97 self.__server.serverPort()
98 )
99
100 def hasConnection(self, senderIp, senderPort = -1):
101 """
102 Public method to check for an existing connection.
103
104 @param senderIp address of the sender (QHostAddress)
105 @param senderPort port of the sender (integer)
106 @return flag indicating an existing connection (boolean)
107 """
108 if senderPort == -1:
109 return senderIp in self.__peers
110
111 if senderIp not in self.__peers:
112 return False
113
114 for connection in self.__peers[senderIp]:
115 if connection.peerPort() == senderPort:
116 return True
117
118 return False
119
120 def hasConnections(self):
121 """
122 Public method to check, if there are any connections established.
123
124 @return flag indicating the presence of connections (boolean)
125 """
126 for connectionList in self.__peers.values():
127 if connectionList:
128 return True
129
130 return False
131
132 def __removeConnection(self, connection):
133 """
134 Private method to remove a connection.
135
136 @param connection reference to the connection to be removed (Connection)
137 """
138 if connection.peerAddress() in self.__peers and \
139 connection in self.__peers[connection.peerAddress()]:
140 self.__peers[connection.peerAddress()].remove(connection)
141 nick = connection.name()
142 if nick != "":
143 self.participantLeft.emit(nick)
144
145 connection.deleteLater()
146
147 def disconnectConnections(self):
148 """
149 Public slot to disconnect from the chat network.
150 """
151 for connectionList in self.__peers.values():
152 while connectionList:
153 self.__removeConnection(connectionList[0])
154
155 def __newConnection(self, connection):
156 """
157 Private slot to handle a new connection.
158
159 @param connection reference to the new connection (Connection)
160 """
161 connection.setGreetingMessage(self.__username,
162 self.__server.serverPort())
163
164 connection.error.connect(self.__connectionError)
165 connection.disconnected.connect(self.__disconnected)
166 connection.readyForUse.connect(self.__readyForUse)
167
168 def __connectionError(self, socketError):
169 """
170 Private slot to handle a connection error.
171
172 @param socketError reference to the error object (QAbstractSocket.SocketError)
173 """
174 connection = self.sender()
175 if socketError != QAbstractSocket.RemoteHostClosedError:
176 if connection.peerPort() != 0:
177 msg = "{0}:{1}\n{2}\n".format(
178 connection.peerAddress().toString(),
179 connection.peerPort(),
180 connection.errorString()
181 )
182 else:
183 msg = "{0}\n".format(connection.errorString())
184 self.connectionError.emit(msg)
185 if connection == self.__initialConnection:
186 self.cannotConnect.emit()
187 self.__removeConnection(connection)
188
189 def __disconnected(self):
190 """
191 Private slot to handle the disconnection of a chat client.
192 """
193 connection = self.sender()
194 self.__removeConnection(connection)
195
196 def __readyForUse(self):
197 """
198 Private slot to handle a connection getting ready for use.
199 """
200 connection = self.sender()
201 if self.hasConnection(connection.peerAddress(), connection.peerPort()):
202 return
203
204 connection.newMessage.connect(self.newMessage)
205 connection.getParticipants.connect(self.__getParticipants)
206
207 self.__peers[connection.peerAddress()].append(connection)
208 nick = connection.name()
209 if nick != "":
210 self.newParticipant.emit(nick)
211
212 if connection == self.__initialConnection:
213 connection.sendGetParticipants()
214 self.__initialConnection = None
215
216 def connectToHost(self, host, port):
217 """
218 Public method to connect to a host.
219
220 @param host host to connect to (string)
221 @param port port to connect to (integer)
222 """
223 self.__initialConnection = Connection(self)
224 self.__newConnection(self.__initialConnection)
225 self.__initialConnection.participants.connect(self.__processParticipants)
226 self.__initialConnection.connectToHost(host, port)
227
228 def __getParticipants(self):
229 """
230 Private slot to handle the request for a list of participants.
231 """
232 reqConnection = self.sender()
233 participants = []
234 for connectionList in self.__peers.values():
235 for connection in connectionList:
236 if connection != reqConnection:
237 participants.append("{0}:{1}".format(
238 connection.peerAddress().toString(), connection.serverPort()))
239 reqConnection.sendParticipants(participants)
240
241 def __processParticipants(self, participants):
242 """
243 Private slot to handle the receipt of a list of participants.
244
245 @param participants list of participants (list of strings of "host:port")
246 """
247 for participant in participants:
248 host, port = participant.split(":")
249 port = int(port)
250
251 if port == 0:
252 msg = self.trUtf8("Illegal address: {0}:{1}\n").format(host, port)
253 self.connectionError.emit(msg)
254 else:
255 if not self.hasConnection(QHostAddress(host), port):
256 connection = Connection(self)
257 self.__newConnection(connection)
258 connection.connectToHost(host, port)

eric ide

mercurial